Add emissive material support and ocean/landscape commands

New MCP commands:
- set_actor_emissive_material: Apply glowing materials for sun, lights, screens
- spawn_ocean: Create WaterBodyOcean actor (requires Water plugin)
- set_ocean_waves: Configure Gerstner wave parameters
- create_landscape: Add minimal landscape for Water plugin support

Improvements:
- SceneCapture2D now renders atmosphere with correct ShowFlags
- DirectionalLight configured as atmosphere sun (SetAtmosphereSunLight)
- Added Water and Landscape module dependencies

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
jamestagg 2026-01-15 23:20:47 -06:00
parent a568b60783
commit cd3dd56f26
8 changed files with 1595 additions and 30 deletions

View File

@ -10,10 +10,37 @@ r.DefaultFeature.AmbientOcclusion=True
r.DefaultFeature.AmbientOcclusionStaticFraction=True r.DefaultFeature.AmbientOcclusionStaticFraction=True
r.Lumen.DiffuseIndirect.Allow=True r.Lumen.DiffuseIndirect.Allow=True
r.Lumen.Reflections.Allow=True r.Lumen.Reflections.Allow=True
r.RayTracing=True r.RayTracing=False
[/Script/WindowsTargetPlatform.WindowsTargetSettings] [/Script/WindowsTargetPlatform.WindowsTargetSettings]
DefaultGraphicsRHI=DefaultGraphicsRHI_DX12 DefaultGraphicsRHI=DefaultGraphicsRHI_DX12
-D3D12TargetedShaderFormats=PCD3D_SM5
+D3D12TargetedShaderFormats=PCD3D_SM5
-D3D11TargetedShaderFormats=PCD3D_SM5
+D3D11TargetedShaderFormats=PCD3D_SM5
bGenerateNaniteFallbackMeshes=True
Compiler=Default
AudioSampleRate=48000
AudioCallbackBufferFrameSize=1024
AudioNumBuffersToEnqueue=1
AudioMaxChannels=0
AudioNumSourceWorkers=4
SpatializationPlugin=
SourceDataOverridePlugin=
ReverbPlugin=
OcclusionPlugin=
CompressionOverrides=(bOverrideCompressionTimes=False,DurationThreshold=5.000000,MaxNumRandomBranches=0,SoundCueQualityIndex=0)
CacheSizeKB=65536
MaxChunkSizeOverrideKB=0
bResampleForDevice=False
MaxSampleRate=48000.000000
HighSampleRate=32000.000000
MedSampleRate=24000.000000
LowSampleRate=12000.000000
MinSampleRate=8000.000000
CompressionQualityModifier=1.000000
AutoStreamingThreshold=0.000000
SoundCueCookQualityIndex=-1
[/Script/Engine.Engine] [/Script/Engine.Engine]
+ActiveGameNameRedirects=(OldGameName="TP_Blank",NewGameName="/Script/Ralpha") +ActiveGameNameRedirects=(OldGameName="TP_Blank",NewGameName="/Script/Ralpha")
@ -27,6 +54,9 @@ gc.MaxObjectsNotConsideredByGC=1
[ConsoleVariables] [ConsoleVariables]
r.Lumen.ScreenProbeGather.FullResolutionJitterWidth=1 r.Lumen.ScreenProbeGather.FullResolutionJitterWidth=1
r.RayTracing.EnableOnDemandShaderCompilation=0
r.D3D12.DXR.AllowSM6ShaderWarning=0
r.SkinCache.CompileShaders=1
[/Script/AndroidFileServerEditor.AndroidFileServerRuntimeSettings] [/Script/AndroidFileServerEditor.AndroidFileServerRuntimeSettings]
bEnablePlugin=True bEnablePlugin=True

View File

@ -350,6 +350,44 @@ FString URalphaMCPServer::ProcessCommand(const FString& JsonCommand)
bool bSuccess = ParameterBridge->DeleteActor(ActorId); bool bSuccess = ParameterBridge->DeleteActor(ActorId);
ResponseObject->SetBoolField(TEXT("success"), bSuccess); 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")) else if (CommandType == TEXT("set_actor_material"))
{ {
FString ActorId = JsonObject->GetStringField(TEXT("actor_id")); FString ActorId = JsonObject->GetStringField(TEXT("actor_id"));
@ -383,6 +421,28 @@ FString URalphaMCPServer::ProcessCommand(const FString& JsonCommand)
bool bSuccess = ParameterBridge->SetActorSimpleMaterial(ActorId, BaseColor, Metallic, Roughness, Opacity); bool bSuccess = ParameterBridge->SetActorSimpleMaterial(ActorId, BaseColor, Metallic, Roughness, Opacity);
ResponseObject->SetBoolField(TEXT("success"), bSuccess); 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")) else if (CommandType == TEXT("list_actors"))
{ {
FString ClassFilter = JsonObject->HasField(TEXT("class_filter")) ? JsonObject->GetStringField(TEXT("class_filter")) : TEXT(""); FString ClassFilter = JsonObject->HasField(TEXT("class_filter")) ? JsonObject->GetStringField(TEXT("class_filter")) : TEXT("");
@ -457,6 +517,367 @@ FString URalphaMCPServer::ProcessCommand(const FString& JsonCommand)
ResponseObject->SetStringField(TEXT("error"), TEXT("Only available in editor")); ResponseObject->SetStringField(TEXT("error"), TEXT("Only available in editor"));
#endif #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 else
{ {
ResponseObject->SetBoolField(TEXT("success"), false); ResponseObject->SetBoolField(TEXT("success"), false);

View File

@ -25,6 +25,30 @@
#include "Dom/JsonValue.h" #include "Dom/JsonValue.h"
#include "Serialization/JsonSerializer.h" #include "Serialization/JsonSerializer.h"
#include "Materials/MaterialInstanceDynamic.h" #include "Materials/MaterialInstanceDynamic.h"
#include "WaterBodyOceanActor.h"
#include "WaterBodyActor.h"
#include "WaterBodyComponent.h"
#include "GerstnerWaterWaves.h"
#include "Landscape.h"
#include "LandscapeProxy.h"
#include "LandscapeInfo.h"
#include "Engine/PointLight.h"
#include "Engine/SpotLight.h"
#include "Engine/RectLight.h"
#include "Components/PointLightComponent.h"
#include "Components/SpotLightComponent.h"
#include "Components/RectLightComponent.h"
#include "Animation/SkeletalMeshActor.h"
#include "Components/SkeletalMeshComponent.h"
#include "Animation/AnimSequence.h"
#include "Animation/AnimSingleNodeInstance.h"
#include "Engine/SkeletalMesh.h"
#include "Engine/SphereReflectionCapture.h"
#include "Engine/BoxReflectionCapture.h"
#include "Components/ReflectionCaptureComponent.h"
#include "Components/SphereReflectionCaptureComponent.h"
#include "Components/BoxReflectionCaptureComponent.h"
#include "Engine/TextureLightProfile.h"
URalphaParameterBridge::URalphaParameterBridge() URalphaParameterBridge::URalphaParameterBridge()
{ {
@ -769,6 +793,121 @@ bool URalphaParameterBridge::DeleteActor(const FString& ActorId)
return false; return false;
} }
bool URalphaParameterBridge::SpawnOcean(const FVector& Location, float Size, FString& OutActorId)
{
UWorld* World = GetCurrentWorld();
if (!World) return false;
FActorSpawnParameters SpawnParams;
SpawnParams.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AlwaysSpawn;
AWaterBodyOcean* Ocean = World->SpawnActor<AWaterBodyOcean>(Location, FRotator::ZeroRotator, SpawnParams);
if (!Ocean)
{
UE_LOG(LogRalphaMCP, Warning, TEXT("Failed to spawn WaterBodyOcean"));
return false;
}
#if WITH_EDITOR
Ocean->SetActorLabel(TEXT("Ralpha_Ocean"));
#endif
OutActorId = Ocean->GetName();
UE_LOG(LogRalphaMCP, Log, TEXT("Spawned WaterBodyOcean: %s at (%f, %f, %f)"), *OutActorId, Location.X, Location.Y, Location.Z);
return true;
}
bool URalphaParameterBridge::SetOceanWaves(float WaveAmplitude, float WaveLength, float Steepness, int32 NumWaves)
{
UWorld* World = GetCurrentWorld();
if (!World) return false;
// Find the ocean actor
AWaterBodyOcean* Ocean = nullptr;
for (TActorIterator<AWaterBodyOcean> It(World); It; ++It)
{
Ocean = *It;
break;
}
if (!Ocean)
{
UE_LOG(LogRalphaMCP, Warning, TEXT("SetOceanWaves: No WaterBodyOcean found in scene"));
return false;
}
// Create a new GerstnerWaterWaves with a simple generator
UGerstnerWaterWaves* WaterWaves = NewObject<UGerstnerWaterWaves>(Ocean);
if (!WaterWaves)
{
UE_LOG(LogRalphaMCP, Warning, TEXT("SetOceanWaves: Failed to create GerstnerWaterWaves"));
return false;
}
// Create and configure the wave generator
UGerstnerWaterWaveGeneratorSimple* WaveGenerator = NewObject<UGerstnerWaterWaveGeneratorSimple>(WaterWaves);
if (WaveGenerator)
{
WaveGenerator->NumWaves = NumWaves;
WaveGenerator->MinWavelength = WaveLength * 0.5f;
WaveGenerator->MaxWavelength = WaveLength * 2.0f;
WaveGenerator->MinAmplitude = WaveAmplitude * 0.3f;
WaveGenerator->MaxAmplitude = WaveAmplitude;
WaveGenerator->SmallWaveSteepness = Steepness;
WaveGenerator->LargeWaveSteepness = Steepness * 0.5f;
WaveGenerator->WindAngleDeg = -90.0f; // Wind from west
WaveGenerator->DirectionAngularSpreadDeg = 90.0f;
WaterWaves->GerstnerWaveGenerator = WaveGenerator;
}
// Recompute waves based on the generator settings
WaterWaves->RecomputeWaves(false);
// Assign to water body actor
Ocean->SetWaterWaves(WaterWaves);
UE_LOG(LogRalphaMCP, Log, TEXT("SetOceanWaves: Configured %d waves (Amplitude=%f, WaveLength=%f, Steepness=%f)"),
NumWaves, WaveAmplitude, WaveLength, Steepness);
return true;
}
bool URalphaParameterBridge::CreateLandscape(float Size, float HeightOffset)
{
UWorld* World = GetCurrentWorld();
if (!World) return false;
// Check if landscape already exists
for (TActorIterator<ALandscape> It(World); It; ++It)
{
UE_LOG(LogRalphaMCP, Log, TEXT("CreateLandscape: Landscape already exists"));
return true;
}
// Create a minimal landscape
// Note: Creating landscapes programmatically is complex, using simpler approach
FActorSpawnParameters SpawnParams;
SpawnParams.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AlwaysSpawn;
FVector Location(0.0f, 0.0f, HeightOffset);
ALandscape* Landscape = World->SpawnActor<ALandscape>(Location, FRotator::ZeroRotator, SpawnParams);
if (!Landscape)
{
UE_LOG(LogRalphaMCP, Warning, TEXT("CreateLandscape: Failed to spawn landscape actor"));
return false;
}
#if WITH_EDITOR
Landscape->SetActorLabel(TEXT("Ralpha_Landscape"));
#endif
UE_LOG(LogRalphaMCP, Log, TEXT("CreateLandscape: Created landscape at Z=%f"), HeightOffset);
return true;
}
bool URalphaParameterBridge::SetActorMaterial(const FString& ActorId, const FString& MaterialPath, int32 MaterialIndex) bool URalphaParameterBridge::SetActorMaterial(const FString& ActorId, const FString& MaterialPath, int32 MaterialIndex)
{ {
UWorld* World = GetCurrentWorld(); UWorld* World = GetCurrentWorld();
@ -893,6 +1032,81 @@ bool URalphaParameterBridge::SetActorSimpleMaterial(const FString& ActorId, cons
return false; return false;
} }
bool URalphaParameterBridge::SetActorEmissiveMaterial(const FString& ActorId, const FLinearColor& EmissiveColor, float EmissiveIntensity)
{
UWorld* World = GetCurrentWorld();
if (!World) return false;
// Find the actor
AActor* TargetActor = nullptr;
for (TActorIterator<AActor> It(World); It; ++It)
{
if (It->GetName() == ActorId)
{
TargetActor = *It;
break;
}
}
if (!TargetActor)
{
UE_LOG(LogRalphaMCP, Warning, TEXT("SetActorEmissiveMaterial: Actor not found: %s"), *ActorId);
return false;
}
// Create a new material from scratch that is unlit/emissive
// We need to use a material that supports emissive - let's try the basic shape material
// and set it to use emissive only
UMaterialInterface* BaseMaterial = Cast<UMaterialInterface>(StaticLoadObject(UMaterialInterface::StaticClass(), nullptr, TEXT("/Engine/EngineMaterials/DefaultMaterial.DefaultMaterial")));
if (!BaseMaterial)
{
UE_LOG(LogRalphaMCP, Warning, TEXT("SetActorEmissiveMaterial: Failed to load default material"));
return false;
}
// Create a dynamic material instance
UMaterialInstanceDynamic* DynMaterial = UMaterialInstanceDynamic::Create(BaseMaterial, TargetActor);
if (!DynMaterial)
{
UE_LOG(LogRalphaMCP, Warning, TEXT("SetActorEmissiveMaterial: Failed to create dynamic material"));
return false;
}
// Calculate emissive color with intensity
FLinearColor ScaledEmissive = EmissiveColor * EmissiveIntensity;
// Try to set emissive parameters with common names
DynMaterial->SetVectorParameterValue(TEXT("EmissiveColor"), ScaledEmissive);
DynMaterial->SetVectorParameterValue(TEXT("Emissive"), ScaledEmissive);
DynMaterial->SetVectorParameterValue(TEXT("Emissive Color"), ScaledEmissive);
// Also try setting base color for materials that blend base with emissive
DynMaterial->SetVectorParameterValue(TEXT("BaseColor"), EmissiveColor);
DynMaterial->SetVectorParameterValue(TEXT("Color"), EmissiveColor);
// Apply to the actor's mesh component
AStaticMeshActor* MeshActor = Cast<AStaticMeshActor>(TargetActor);
if (MeshActor && MeshActor->GetStaticMeshComponent())
{
MeshActor->GetStaticMeshComponent()->SetMaterial(0, DynMaterial);
UE_LOG(LogRalphaMCP, Log, TEXT("SetActorEmissiveMaterial: Applied emissive material to %s (Color: R=%.2f G=%.2f B=%.2f, Intensity=%.2f)"),
*ActorId, EmissiveColor.R, EmissiveColor.G, EmissiveColor.B, EmissiveIntensity);
return true;
}
// Try generic primitive component
UPrimitiveComponent* PrimComp = TargetActor->FindComponentByClass<UPrimitiveComponent>();
if (PrimComp)
{
PrimComp->SetMaterial(0, DynMaterial);
UE_LOG(LogRalphaMCP, Log, TEXT("SetActorEmissiveMaterial: Applied emissive material to %s (primitive)"), *ActorId);
return true;
}
UE_LOG(LogRalphaMCP, Warning, TEXT("SetActorEmissiveMaterial: No suitable component found on %s"), *ActorId);
return false;
}
TArray<TSharedPtr<FJsonValue>> URalphaParameterBridge::ListActors(const FString& ClassFilter, const FString& NameFilter, bool bIncludeTransforms) TArray<TSharedPtr<FJsonValue>> URalphaParameterBridge::ListActors(const FString& ClassFilter, const FString& NameFilter, bool bIncludeTransforms)
{ {
TArray<TSharedPtr<FJsonValue>> Results; TArray<TSharedPtr<FJsonValue>> Results;
@ -1119,15 +1333,16 @@ TSharedPtr<FJsonObject> URalphaParameterBridge::SetupScene()
} }
// Check/Create DirectionalLight // Check/Create DirectionalLight
if (!FindDirectionalLight()) ADirectionalLight* Sun = FindDirectionalLight();
if (!Sun)
{ {
FActorSpawnParameters SpawnParams; FActorSpawnParameters SpawnParams;
SpawnParams.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AlwaysSpawn; SpawnParams.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AlwaysSpawn;
ADirectionalLight* Light = World->SpawnActor<ADirectionalLight>(FVector::ZeroVector, FRotator(-45.0f, 0.0f, 0.0f), SpawnParams); Sun = World->SpawnActor<ADirectionalLight>(FVector::ZeroVector, FRotator(-45.0f, 0.0f, 0.0f), SpawnParams);
if (Light) if (Sun)
{ {
#if WITH_EDITOR #if WITH_EDITOR
Light->SetActorLabel(TEXT("Ralpha_Sun")); Sun->SetActorLabel(TEXT("Ralpha_Sun"));
#endif #endif
CreatedActors.Add(TEXT("DirectionalLight")); CreatedActors.Add(TEXT("DirectionalLight"));
} }
@ -1137,6 +1352,17 @@ TSharedPtr<FJsonObject> URalphaParameterBridge::SetupScene()
ExistingActors.Add(TEXT("DirectionalLight")); ExistingActors.Add(TEXT("DirectionalLight"));
} }
// Configure DirectionalLight as atmosphere sun for visible sun disc
if (Sun)
{
UDirectionalLightComponent* LightComp = Cast<UDirectionalLightComponent>(Sun->GetLightComponent());
if (LightComp)
{
LightComp->SetAtmosphereSunLight(true);
LightComp->SetAtmosphereSunLightIndex(0);
}
}
// Check/Create SkyLight // Check/Create SkyLight
if (!FindSkyLight()) if (!FindSkyLight())
{ {
@ -1262,3 +1488,672 @@ TSharedPtr<FJsonObject> URalphaParameterBridge::SetupScene()
return Result; return Result;
} }
// ============================================================================
// Point Light
// ============================================================================
bool URalphaParameterBridge::SpawnPointLight(const FVector& Location, float Intensity, const FLinearColor& Color, float AttenuationRadius, const FString& ActorLabel, FString& OutActorId)
{
UWorld* World = GetCurrentWorld();
if (!World) return false;
FActorSpawnParameters SpawnParams;
SpawnParams.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AlwaysSpawn;
APointLight* Light = World->SpawnActor<APointLight>(Location, FRotator::ZeroRotator, SpawnParams);
if (!Light)
{
UE_LOG(LogRalphaMCP, Warning, TEXT("Failed to spawn PointLight"));
return false;
}
UPointLightComponent* LightComp = Light->PointLightComponent;
if (LightComp)
{
LightComp->SetIntensity(Intensity);
LightComp->SetLightColor(Color);
LightComp->SetAttenuationRadius(AttenuationRadius);
}
#if WITH_EDITOR
if (!ActorLabel.IsEmpty())
{
Light->SetActorLabel(ActorLabel);
}
#endif
OutActorId = Light->GetName();
UE_LOG(LogRalphaMCP, Log, TEXT("Spawned PointLight: %s at (%f, %f, %f)"), *OutActorId, Location.X, Location.Y, Location.Z);
return true;
}
bool URalphaParameterBridge::SetPointLightParameters(const FString& ActorId, const TSharedPtr<FJsonObject>& Params)
{
UWorld* World = GetCurrentWorld();
if (!World) return false;
APointLight* Light = nullptr;
for (TActorIterator<APointLight> It(World); It; ++It)
{
if (It->GetName() == ActorId)
{
Light = *It;
break;
}
}
if (!Light)
{
UE_LOG(LogRalphaMCP, Warning, TEXT("SetPointLightParameters: PointLight not found: %s"), *ActorId);
return false;
}
UPointLightComponent* LightComp = Light->PointLightComponent;
if (!LightComp) return false;
if (Params->HasField(TEXT("intensity")))
{
LightComp->SetIntensity(Params->GetNumberField(TEXT("intensity")));
}
if (Params->HasField(TEXT("color")))
{
FLinearColor Color = ParseHexColor(Params->GetStringField(TEXT("color")));
LightComp->SetLightColor(Color);
}
if (Params->HasField(TEXT("attenuation_radius")))
{
LightComp->SetAttenuationRadius(Params->GetNumberField(TEXT("attenuation_radius")));
}
if (Params->HasField(TEXT("source_radius")))
{
LightComp->SourceRadius = Params->GetNumberField(TEXT("source_radius"));
}
if (Params->HasField(TEXT("soft_source_radius")))
{
LightComp->SoftSourceRadius = Params->GetNumberField(TEXT("soft_source_radius"));
}
if (Params->HasField(TEXT("temperature")))
{
LightComp->bUseTemperature = true;
LightComp->SetTemperature(Params->GetNumberField(TEXT("temperature")));
}
// Location update
const TSharedPtr<FJsonObject>* LocationObj;
if (Params->TryGetObjectField(TEXT("location"), LocationObj))
{
FVector Location = Light->GetActorLocation();
if ((*LocationObj)->HasField(TEXT("x"))) Location.X = (*LocationObj)->GetNumberField(TEXT("x"));
if ((*LocationObj)->HasField(TEXT("y"))) Location.Y = (*LocationObj)->GetNumberField(TEXT("y"));
if ((*LocationObj)->HasField(TEXT("z"))) Location.Z = (*LocationObj)->GetNumberField(TEXT("z"));
Light->SetActorLocation(Location);
}
LightComp->MarkRenderStateDirty();
return true;
}
// ============================================================================
// Spot Light
// ============================================================================
bool URalphaParameterBridge::SpawnSpotLight(const FVector& Location, const FRotator& Rotation, float Intensity, const FLinearColor& Color, float AttenuationRadius, float InnerConeAngle, float OuterConeAngle, const FString& ActorLabel, FString& OutActorId)
{
UWorld* World = GetCurrentWorld();
if (!World) return false;
FActorSpawnParameters SpawnParams;
SpawnParams.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AlwaysSpawn;
ASpotLight* Light = World->SpawnActor<ASpotLight>(Location, Rotation, SpawnParams);
if (!Light)
{
UE_LOG(LogRalphaMCP, Warning, TEXT("Failed to spawn SpotLight"));
return false;
}
USpotLightComponent* LightComp = Cast<USpotLightComponent>(Light->GetLightComponent());
if (LightComp)
{
LightComp->SetIntensity(Intensity);
LightComp->SetLightColor(Color);
LightComp->SetAttenuationRadius(AttenuationRadius);
LightComp->SetInnerConeAngle(InnerConeAngle);
LightComp->SetOuterConeAngle(OuterConeAngle);
}
#if WITH_EDITOR
if (!ActorLabel.IsEmpty())
{
Light->SetActorLabel(ActorLabel);
}
#endif
OutActorId = Light->GetName();
UE_LOG(LogRalphaMCP, Log, TEXT("Spawned SpotLight: %s at (%f, %f, %f)"), *OutActorId, Location.X, Location.Y, Location.Z);
return true;
}
bool URalphaParameterBridge::SetSpotLightParameters(const FString& ActorId, const TSharedPtr<FJsonObject>& Params)
{
UWorld* World = GetCurrentWorld();
if (!World) return false;
ASpotLight* Light = nullptr;
for (TActorIterator<ASpotLight> It(World); It; ++It)
{
if (It->GetName() == ActorId)
{
Light = *It;
break;
}
}
if (!Light)
{
UE_LOG(LogRalphaMCP, Warning, TEXT("SetSpotLightParameters: SpotLight not found: %s"), *ActorId);
return false;
}
USpotLightComponent* LightComp = Cast<USpotLightComponent>(Light->GetLightComponent());
if (!LightComp) return false;
if (Params->HasField(TEXT("intensity")))
{
LightComp->SetIntensity(Params->GetNumberField(TEXT("intensity")));
}
if (Params->HasField(TEXT("color")))
{
FLinearColor Color = ParseHexColor(Params->GetStringField(TEXT("color")));
LightComp->SetLightColor(Color);
}
if (Params->HasField(TEXT("attenuation_radius")))
{
LightComp->SetAttenuationRadius(Params->GetNumberField(TEXT("attenuation_radius")));
}
if (Params->HasField(TEXT("inner_cone_angle")))
{
LightComp->SetInnerConeAngle(Params->GetNumberField(TEXT("inner_cone_angle")));
}
if (Params->HasField(TEXT("outer_cone_angle")))
{
LightComp->SetOuterConeAngle(Params->GetNumberField(TEXT("outer_cone_angle")));
}
if (Params->HasField(TEXT("source_radius")))
{
LightComp->SourceRadius = Params->GetNumberField(TEXT("source_radius"));
}
if (Params->HasField(TEXT("temperature")))
{
LightComp->bUseTemperature = true;
LightComp->SetTemperature(Params->GetNumberField(TEXT("temperature")));
}
// Location update
const TSharedPtr<FJsonObject>* LocationObj;
if (Params->TryGetObjectField(TEXT("location"), LocationObj))
{
FVector Location = Light->GetActorLocation();
if ((*LocationObj)->HasField(TEXT("x"))) Location.X = (*LocationObj)->GetNumberField(TEXT("x"));
if ((*LocationObj)->HasField(TEXT("y"))) Location.Y = (*LocationObj)->GetNumberField(TEXT("y"));
if ((*LocationObj)->HasField(TEXT("z"))) Location.Z = (*LocationObj)->GetNumberField(TEXT("z"));
Light->SetActorLocation(Location);
}
// Rotation update
const TSharedPtr<FJsonObject>* RotationObj;
if (Params->TryGetObjectField(TEXT("rotation"), RotationObj))
{
FRotator Rotation = Light->GetActorRotation();
if ((*RotationObj)->HasField(TEXT("pitch"))) Rotation.Pitch = (*RotationObj)->GetNumberField(TEXT("pitch"));
if ((*RotationObj)->HasField(TEXT("yaw"))) Rotation.Yaw = (*RotationObj)->GetNumberField(TEXT("yaw"));
if ((*RotationObj)->HasField(TEXT("roll"))) Rotation.Roll = (*RotationObj)->GetNumberField(TEXT("roll"));
Light->SetActorRotation(Rotation);
}
LightComp->MarkRenderStateDirty();
return true;
}
// ============================================================================
// Rect Light
// ============================================================================
bool URalphaParameterBridge::SpawnRectLight(const FVector& Location, const FRotator& Rotation, float Intensity, const FLinearColor& Color, float SourceWidth, float SourceHeight, float AttenuationRadius, const FString& ActorLabel, FString& OutActorId)
{
UWorld* World = GetCurrentWorld();
if (!World) return false;
FActorSpawnParameters SpawnParams;
SpawnParams.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AlwaysSpawn;
ARectLight* Light = World->SpawnActor<ARectLight>(Location, Rotation, SpawnParams);
if (!Light)
{
UE_LOG(LogRalphaMCP, Warning, TEXT("Failed to spawn RectLight"));
return false;
}
URectLightComponent* LightComp = Cast<URectLightComponent>(Light->GetLightComponent());
if (LightComp)
{
LightComp->SetIntensity(Intensity);
LightComp->SetLightColor(Color);
LightComp->SetSourceWidth(SourceWidth);
LightComp->SetSourceHeight(SourceHeight);
LightComp->SetAttenuationRadius(AttenuationRadius);
}
#if WITH_EDITOR
if (!ActorLabel.IsEmpty())
{
Light->SetActorLabel(ActorLabel);
}
#endif
OutActorId = Light->GetName();
UE_LOG(LogRalphaMCP, Log, TEXT("Spawned RectLight: %s at (%f, %f, %f)"), *OutActorId, Location.X, Location.Y, Location.Z);
return true;
}
bool URalphaParameterBridge::SetRectLightParameters(const FString& ActorId, const TSharedPtr<FJsonObject>& Params)
{
UWorld* World = GetCurrentWorld();
if (!World) return false;
ARectLight* RectLight = nullptr;
for (TActorIterator<ARectLight> It(World); It; ++It)
{
if (It->GetName() == ActorId)
{
RectLight = *It;
break;
}
}
if (!RectLight)
{
UE_LOG(LogRalphaMCP, Warning, TEXT("SetRectLightParameters: RectLight not found: %s"), *ActorId);
return false;
}
URectLightComponent* LightComp = Cast<URectLightComponent>(RectLight->GetLightComponent());
if (!LightComp) return false;
if (Params->HasField(TEXT("intensity")))
{
LightComp->SetIntensity(Params->GetNumberField(TEXT("intensity")));
}
if (Params->HasField(TEXT("color")))
{
FLinearColor Color = ParseHexColor(Params->GetStringField(TEXT("color")));
LightComp->SetLightColor(Color);
}
if (Params->HasField(TEXT("source_width")))
{
LightComp->SetSourceWidth(Params->GetNumberField(TEXT("source_width")));
}
if (Params->HasField(TEXT("source_height")))
{
LightComp->SetSourceHeight(Params->GetNumberField(TEXT("source_height")));
}
if (Params->HasField(TEXT("attenuation_radius")))
{
LightComp->SetAttenuationRadius(Params->GetNumberField(TEXT("attenuation_radius")));
}
if (Params->HasField(TEXT("barn_door_angle")))
{
LightComp->BarnDoorAngle = Params->GetNumberField(TEXT("barn_door_angle"));
}
if (Params->HasField(TEXT("barn_door_length")))
{
LightComp->BarnDoorLength = Params->GetNumberField(TEXT("barn_door_length"));
}
if (Params->HasField(TEXT("temperature")))
{
LightComp->bUseTemperature = true;
LightComp->SetTemperature(Params->GetNumberField(TEXT("temperature")));
}
// Location update
const TSharedPtr<FJsonObject>* LocationObj;
if (Params->TryGetObjectField(TEXT("location"), LocationObj))
{
FVector Location = RectLight->GetActorLocation();
if ((*LocationObj)->HasField(TEXT("x"))) Location.X = (*LocationObj)->GetNumberField(TEXT("x"));
if ((*LocationObj)->HasField(TEXT("y"))) Location.Y = (*LocationObj)->GetNumberField(TEXT("y"));
if ((*LocationObj)->HasField(TEXT("z"))) Location.Z = (*LocationObj)->GetNumberField(TEXT("z"));
RectLight->SetActorLocation(Location);
}
// Rotation update
const TSharedPtr<FJsonObject>* RotationObj;
if (Params->TryGetObjectField(TEXT("rotation"), RotationObj))
{
FRotator Rotation = RectLight->GetActorRotation();
if ((*RotationObj)->HasField(TEXT("pitch"))) Rotation.Pitch = (*RotationObj)->GetNumberField(TEXT("pitch"));
if ((*RotationObj)->HasField(TEXT("yaw"))) Rotation.Yaw = (*RotationObj)->GetNumberField(TEXT("yaw"));
if ((*RotationObj)->HasField(TEXT("roll"))) Rotation.Roll = (*RotationObj)->GetNumberField(TEXT("roll"));
RectLight->SetActorRotation(Rotation);
}
LightComp->MarkRenderStateDirty();
return true;
}
// ============================================================================
// Skeletal Mesh
// ============================================================================
bool URalphaParameterBridge::SpawnSkeletalMesh(const FString& SkeletalMeshPath, const FVector& Location, const FRotator& Rotation, float Scale, const FString& ActorLabel, FString& OutActorId)
{
UWorld* World = GetCurrentWorld();
if (!World) return false;
// Load the skeletal mesh
USkeletalMesh* SkeletalMesh = Cast<USkeletalMesh>(StaticLoadObject(USkeletalMesh::StaticClass(), nullptr, *SkeletalMeshPath));
if (!SkeletalMesh)
{
UE_LOG(LogRalphaMCP, Warning, TEXT("Failed to load skeletal mesh: %s"), *SkeletalMeshPath);
return false;
}
FActorSpawnParameters SpawnParams;
SpawnParams.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AlwaysSpawn;
ASkeletalMeshActor* Actor = World->SpawnActor<ASkeletalMeshActor>(Location, Rotation, SpawnParams);
if (!Actor)
{
UE_LOG(LogRalphaMCP, Warning, TEXT("Failed to spawn SkeletalMeshActor"));
return false;
}
USkeletalMeshComponent* MeshComp = Actor->GetSkeletalMeshComponent();
if (MeshComp)
{
MeshComp->SetSkeletalMesh(SkeletalMesh);
MeshComp->SetAnimationMode(EAnimationMode::AnimationSingleNode);
}
Actor->SetActorScale3D(FVector(Scale));
#if WITH_EDITOR
if (!ActorLabel.IsEmpty())
{
Actor->SetActorLabel(ActorLabel);
}
#endif
OutActorId = Actor->GetName();
UE_LOG(LogRalphaMCP, Log, TEXT("Spawned SkeletalMeshActor: %s at (%f, %f, %f)"), *OutActorId, Location.X, Location.Y, Location.Z);
return true;
}
bool URalphaParameterBridge::PlayAnimation(const FString& ActorId, const FString& AnimationPath, bool bLooping, float PlayRate)
{
UWorld* World = GetCurrentWorld();
if (!World) return false;
ASkeletalMeshActor* Actor = nullptr;
for (TActorIterator<ASkeletalMeshActor> It(World); It; ++It)
{
if (It->GetName() == ActorId)
{
Actor = *It;
break;
}
}
if (!Actor)
{
UE_LOG(LogRalphaMCP, Warning, TEXT("PlayAnimation: SkeletalMeshActor not found: %s"), *ActorId);
return false;
}
UAnimSequence* AnimSequence = Cast<UAnimSequence>(StaticLoadObject(UAnimSequence::StaticClass(), nullptr, *AnimationPath));
if (!AnimSequence)
{
UE_LOG(LogRalphaMCP, Warning, TEXT("PlayAnimation: Failed to load animation: %s"), *AnimationPath);
return false;
}
USkeletalMeshComponent* MeshComp = Actor->GetSkeletalMeshComponent();
if (MeshComp)
{
MeshComp->SetAnimationMode(EAnimationMode::AnimationSingleNode);
MeshComp->PlayAnimation(AnimSequence, bLooping);
MeshComp->SetPlayRate(PlayRate);
UE_LOG(LogRalphaMCP, Log, TEXT("PlayAnimation: Playing %s on %s (loop=%d, rate=%f)"), *AnimationPath, *ActorId, bLooping, PlayRate);
return true;
}
return false;
}
bool URalphaParameterBridge::StopAnimation(const FString& ActorId)
{
UWorld* World = GetCurrentWorld();
if (!World) return false;
ASkeletalMeshActor* Actor = nullptr;
for (TActorIterator<ASkeletalMeshActor> It(World); It; ++It)
{
if (It->GetName() == ActorId)
{
Actor = *It;
break;
}
}
if (!Actor)
{
UE_LOG(LogRalphaMCP, Warning, TEXT("StopAnimation: SkeletalMeshActor not found: %s"), *ActorId);
return false;
}
USkeletalMeshComponent* MeshComp = Actor->GetSkeletalMeshComponent();
if (MeshComp)
{
MeshComp->Stop();
return true;
}
return false;
}
bool URalphaParameterBridge::SetAnimationPose(const FString& ActorId, const FString& AnimationPath, float Time)
{
UWorld* World = GetCurrentWorld();
if (!World) return false;
ASkeletalMeshActor* Actor = nullptr;
for (TActorIterator<ASkeletalMeshActor> It(World); It; ++It)
{
if (It->GetName() == ActorId)
{
Actor = *It;
break;
}
}
if (!Actor)
{
UE_LOG(LogRalphaMCP, Warning, TEXT("SetAnimationPose: SkeletalMeshActor not found: %s"), *ActorId);
return false;
}
UAnimSequence* AnimSequence = Cast<UAnimSequence>(StaticLoadObject(UAnimSequence::StaticClass(), nullptr, *AnimationPath));
if (!AnimSequence)
{
UE_LOG(LogRalphaMCP, Warning, TEXT("SetAnimationPose: Failed to load animation: %s"), *AnimationPath);
return false;
}
USkeletalMeshComponent* MeshComp = Actor->GetSkeletalMeshComponent();
if (MeshComp)
{
MeshComp->SetAnimationMode(EAnimationMode::AnimationSingleNode);
MeshComp->SetAnimation(AnimSequence);
MeshComp->SetPosition(Time);
MeshComp->SetPlayRate(0.0f);
UE_LOG(LogRalphaMCP, Log, TEXT("SetAnimationPose: Set %s to pose at time %f"), *ActorId, Time);
return true;
}
return false;
}
// ============================================================================
// Reflection Captures
// ============================================================================
bool URalphaParameterBridge::SpawnSphereReflectionCapture(const FVector& Location, float InfluenceRadius, const FString& ActorLabel, FString& OutActorId)
{
UWorld* World = GetCurrentWorld();
if (!World) return false;
FActorSpawnParameters SpawnParams;
SpawnParams.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AlwaysSpawn;
ASphereReflectionCapture* Capture = World->SpawnActor<ASphereReflectionCapture>(Location, FRotator::ZeroRotator, SpawnParams);
if (!Capture)
{
UE_LOG(LogRalphaMCP, Warning, TEXT("Failed to spawn SphereReflectionCapture"));
return false;
}
USphereReflectionCaptureComponent* CaptureComp = Cast<USphereReflectionCaptureComponent>(Capture->GetCaptureComponent());
if (CaptureComp)
{
CaptureComp->InfluenceRadius = InfluenceRadius;
}
#if WITH_EDITOR
if (!ActorLabel.IsEmpty())
{
Capture->SetActorLabel(ActorLabel);
}
#endif
OutActorId = Capture->GetName();
UE_LOG(LogRalphaMCP, Log, TEXT("Spawned SphereReflectionCapture: %s at (%f, %f, %f)"), *OutActorId, Location.X, Location.Y, Location.Z);
return true;
}
bool URalphaParameterBridge::SpawnBoxReflectionCapture(const FVector& Location, const FVector& BoxExtent, const FString& ActorLabel, FString& OutActorId)
{
UWorld* World = GetCurrentWorld();
if (!World) return false;
FActorSpawnParameters SpawnParams;
SpawnParams.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AlwaysSpawn;
ABoxReflectionCapture* Capture = World->SpawnActor<ABoxReflectionCapture>(Location, FRotator::ZeroRotator, SpawnParams);
if (!Capture)
{
UE_LOG(LogRalphaMCP, Warning, TEXT("Failed to spawn BoxReflectionCapture"));
return false;
}
UBoxReflectionCaptureComponent* CaptureComp = Cast<UBoxReflectionCaptureComponent>(Capture->GetCaptureComponent());
if (CaptureComp)
{
CaptureComp->BoxTransitionDistance = BoxExtent.X;
}
#if WITH_EDITOR
if (!ActorLabel.IsEmpty())
{
Capture->SetActorLabel(ActorLabel);
}
#endif
Capture->SetActorScale3D(BoxExtent / 100.0f);
OutActorId = Capture->GetName();
UE_LOG(LogRalphaMCP, Log, TEXT("Spawned BoxReflectionCapture: %s at (%f, %f, %f)"), *OutActorId, Location.X, Location.Y, Location.Z);
return true;
}
bool URalphaParameterBridge::UpdateReflectionCaptures()
{
UWorld* World = GetCurrentWorld();
if (!World) return false;
#if WITH_EDITOR
for (TActorIterator<AReflectionCapture> It(World); It; ++It)
{
UReflectionCaptureComponent* CaptureComp = It->GetCaptureComponent();
if (CaptureComp)
{
CaptureComp->MarkDirtyForRecapture();
}
}
GEditor->BuildReflectionCaptures();
UE_LOG(LogRalphaMCP, Log, TEXT("UpdateReflectionCaptures: Triggered rebuild"));
return true;
#else
UE_LOG(LogRalphaMCP, Warning, TEXT("UpdateReflectionCaptures: Only available in editor"));
return false;
#endif
}
// ============================================================================
// IES Light Profiles
// ============================================================================
bool URalphaParameterBridge::SetLightIESProfile(const FString& ActorId, const FString& IESProfilePath)
{
UWorld* World = GetCurrentWorld();
if (!World) return false;
UTextureLightProfile* IESProfile = nullptr;
if (!IESProfilePath.IsEmpty())
{
IESProfile = Cast<UTextureLightProfile>(StaticLoadObject(UTextureLightProfile::StaticClass(), nullptr, *IESProfilePath));
if (!IESProfile)
{
UE_LOG(LogRalphaMCP, Warning, TEXT("SetLightIESProfile: Failed to load IES profile: %s"), *IESProfilePath);
return false;
}
}
for (TActorIterator<ALight> It(World); It; ++It)
{
if (It->GetName() == ActorId)
{
ULightComponent* LightComp = It->GetLightComponent();
if (LightComp)
{
LightComp->IESTexture = IESProfile;
LightComp->bUseIESBrightness = (IESProfile != nullptr);
LightComp->MarkRenderStateDirty();
UE_LOG(LogRalphaMCP, Log, TEXT("SetLightIESProfile: Applied IES profile to %s"), *ActorId);
return true;
}
}
}
UE_LOG(LogRalphaMCP, Warning, TEXT("SetLightIESProfile: Light not found: %s"), *ActorId);
return false;
}

View File

@ -16,10 +16,22 @@
#include "Framework/Application/SlateApplication.h" #include "Framework/Application/SlateApplication.h"
#include "Widgets/SWindow.h" #include "Widgets/SWindow.h"
#include "UnrealClient.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 #if WITH_EDITOR
#include "Editor.h" #include "Editor.h"
#include "LevelEditorViewport.h" #include "LevelEditorViewport.h"
#include "SLevelViewport.h"
#include "LevelEditor.h"
#include "ILevelEditor.h"
#endif #endif
URalphaScreenCapture::URalphaScreenCapture() URalphaScreenCapture::URalphaScreenCapture()
@ -28,52 +40,84 @@ URalphaScreenCapture::URalphaScreenCapture()
bool URalphaScreenCapture::CaptureScreenshot(int32 Width, int32 Height, FString& OutBase64, FString& OutFilePath) bool URalphaScreenCapture::CaptureScreenshot(int32 Width, int32 Height, FString& OutBase64, FString& OutFilePath)
{ {
// Get the game viewport
UGameViewportClient* ViewportClient = GEngine ? GEngine->GameViewport : nullptr;
// In editor, we might not have a game viewport, try to get the active viewport
FViewport* Viewport = nullptr; FViewport* Viewport = nullptr;
int32 ViewportWidth = 0;
int32 ViewportHeight = 0;
if (ViewportClient && ViewportClient->Viewport)
{
Viewport = ViewportClient->Viewport;
}
#if WITH_EDITOR #if WITH_EDITOR
else // In editor, find the active level viewport with valid size
{
// Try to get the editor viewport
if (GEditor) if (GEditor)
{ {
// Try to find a viewport with a valid size
for (FLevelEditorViewportClient* LevelVC : GEditor->GetLevelViewportClients()) for (FLevelEditorViewportClient* LevelVC : GEditor->GetLevelViewportClients())
{ {
if (LevelVC && LevelVC->Viewport) if (LevelVC && LevelVC->Viewport)
{
FIntPoint Size = LevelVC->Viewport->GetSizeXY();
if (Size.X > 0 && Size.Y > 0)
{ {
Viewport = LevelVC->Viewport; Viewport = LevelVC->Viewport;
ViewportWidth = Size.X;
ViewportHeight = Size.Y;
UE_LOG(LogRalphaMCP, Log, TEXT("Found editor viewport: %dx%d"), ViewportWidth, ViewportHeight);
break; 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 #endif
// Fallback to game viewport
if (!Viewport) if (!Viewport)
{ {
UE_LOG(LogRalphaMCP, Warning, TEXT("No viewport available for screenshot")); 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; return false;
} }
// Read pixels from the viewport // Read pixels from the viewport
TArray<FColor> Bitmap; TArray<FColor> Bitmap;
int32 ViewportWidth = Viewport->GetSizeXY().X;
int32 ViewportHeight = Viewport->GetSizeXY().Y;
if (ViewportWidth <= 0 || ViewportHeight <= 0)
{
UE_LOG(LogRalphaMCP, Warning, TEXT("Invalid viewport size: %dx%d"), ViewportWidth, ViewportHeight);
return false;
}
// Read the viewport pixels
bool bSuccess = Viewport->ReadPixels(Bitmap); bool bSuccess = Viewport->ReadPixels(Bitmap);
if (!bSuccess || Bitmap.Num() == 0) if (!bSuccess || Bitmap.Num() == 0)
@ -151,6 +195,122 @@ FString URalphaScreenCapture::PixelsToBase64PNG(const TArray<FColor>& Pixels, in
return FBase64::Encode(PNGData32); 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 URalphaScreenCapture::GetScreenshotDirectory()
{ {
FString ScreenshotDir = FPaths::ProjectSavedDir() / TEXT("Screenshots") / TEXT("Ralpha"); FString ScreenshotDir = FPaths::ProjectSavedDir() / TEXT("Screenshots") / TEXT("Ralpha");

View File

@ -67,9 +67,47 @@ public:
// Create a simple material with specified properties and apply to actor // 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); 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 // Scene Setup - creates required actors for rendering control
TSharedPtr<FJsonObject> SetupScene(); 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: private:
// Find actors of specific types in the world // Find actors of specific types in the world
APostProcessVolume* FindPostProcessVolume(); APostProcessVolume* FindPostProcessVolume();

View File

@ -38,6 +38,17 @@ public:
UFUNCTION(BlueprintCallable, Category = "Ralpha") UFUNCTION(BlueprintCallable, Category = "Ralpha")
bool CaptureHighResScreenshot(int32 Width, int32 Height, FString& OutBase64); 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: private:
/** Convert raw pixel data to base64 PNG */ /** Convert raw pixel data to base64 PNG */
FString PixelsToBase64PNG(const TArray<FColor>& Pixels, int32 Width, int32 Height); FString PixelsToBase64PNG(const TArray<FColor>& Pixels, int32 Width, int32 Height);

View File

@ -42,12 +42,18 @@ public class RalphaCore : ModuleRules
"Slate", "Slate",
"SlateCore", "SlateCore",
"CinematicCamera", "CinematicCamera",
"Water",
"Landscape",
} }
); );
if (Target.bBuildEditor) if (Target.bBuildEditor)
{ {
PrivateDependencyModuleNames.Add("UnrealEd"); PrivateDependencyModuleNames.AddRange(new string[]
{
"UnrealEd",
"LevelEditor",
});
} }
DynamicallyLoadedModuleNames.AddRange( DynamicallyLoadedModuleNames.AddRange(

View File

@ -8,6 +8,10 @@
{ {
"Name": "RalphaPlugin", "Name": "RalphaPlugin",
"Enabled": true "Enabled": true
},
{
"Name": "Water",
"Enabled": true
} }
], ],
"TargetPlatforms": [ "TargetPlatforms": [