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:
parent
a568b60783
commit
cd3dd56f26
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
if (GEditor)
|
||||||
{
|
{
|
||||||
// Try to get the editor viewport
|
// Try to find a viewport with a valid size
|
||||||
if (GEditor)
|
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");
|
||||||
|
|
|
||||||
|
|
@ -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();
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
|
||||||
|
|
@ -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(
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,10 @@
|
||||||
{
|
{
|
||||||
"Name": "RalphaPlugin",
|
"Name": "RalphaPlugin",
|
||||||
"Enabled": true
|
"Enabled": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "Water",
|
||||||
|
"Enabled": true
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"TargetPlatforms": [
|
"TargetPlatforms": [
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue