2160 lines
65 KiB
C++
2160 lines
65 KiB
C++
// Copyright Ralpha Team. All Rights Reserved.
|
|
|
|
#include "RalphaParameterBridge.h"
|
|
#include "RalphaMCPServer.h"
|
|
#include "Engine/Engine.h"
|
|
#include "Engine/World.h"
|
|
#include "Engine/PostProcessVolume.h"
|
|
#include "Engine/DirectionalLight.h"
|
|
#include "Engine/SkyLight.h"
|
|
#include "Engine/ExponentialHeightFog.h"
|
|
#include "CineCameraActor.h"
|
|
#include "CineCameraComponent.h"
|
|
#include "Components/PostProcessComponent.h"
|
|
#include "Components/DirectionalLightComponent.h"
|
|
#include "Components/SkyLightComponent.h"
|
|
#include "Components/ExponentialHeightFogComponent.h"
|
|
#include "Kismet/GameplayStatics.h"
|
|
#include "EngineUtils.h"
|
|
#include "AssetRegistry/AssetRegistryModule.h"
|
|
#include "Engine/StaticMeshActor.h"
|
|
#include "Engine/StaticMesh.h"
|
|
#include "Components/SkyAtmosphereComponent.h"
|
|
#include "Components/VolumetricCloudComponent.h"
|
|
#include "Dom/JsonObject.h"
|
|
#include "Dom/JsonValue.h"
|
|
#include "Serialization/JsonSerializer.h"
|
|
#include "Materials/MaterialInstanceDynamic.h"
|
|
#include "WaterBodyOceanActor.h"
|
|
#include "WaterBodyActor.h"
|
|
#include "WaterBodyComponent.h"
|
|
#include "GerstnerWaterWaves.h"
|
|
#include "Landscape.h"
|
|
#include "LandscapeProxy.h"
|
|
#include "LandscapeInfo.h"
|
|
#include "Engine/PointLight.h"
|
|
#include "Engine/SpotLight.h"
|
|
#include "Engine/RectLight.h"
|
|
#include "Components/PointLightComponent.h"
|
|
#include "Components/SpotLightComponent.h"
|
|
#include "Components/RectLightComponent.h"
|
|
#include "Animation/SkeletalMeshActor.h"
|
|
#include "Components/SkeletalMeshComponent.h"
|
|
#include "Animation/AnimSequence.h"
|
|
#include "Animation/AnimSingleNodeInstance.h"
|
|
#include "Engine/SkeletalMesh.h"
|
|
#include "Engine/SphereReflectionCapture.h"
|
|
#include "Engine/BoxReflectionCapture.h"
|
|
#include "Components/ReflectionCaptureComponent.h"
|
|
#include "Components/SphereReflectionCaptureComponent.h"
|
|
#include "Components/BoxReflectionCaptureComponent.h"
|
|
#include "Engine/TextureLightProfile.h"
|
|
|
|
URalphaParameterBridge::URalphaParameterBridge()
|
|
{
|
|
}
|
|
|
|
UWorld* URalphaParameterBridge::GetCurrentWorld()
|
|
{
|
|
if (GEngine && GEngine->GetWorldContexts().Num() > 0)
|
|
{
|
|
for (const FWorldContext& Context : GEngine->GetWorldContexts())
|
|
{
|
|
if (Context.WorldType == EWorldType::Editor || Context.WorldType == EWorldType::PIE)
|
|
{
|
|
return Context.World();
|
|
}
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
APostProcessVolume* URalphaParameterBridge::FindPostProcessVolume()
|
|
{
|
|
UWorld* World = GetCurrentWorld();
|
|
if (!World) return nullptr;
|
|
|
|
for (TActorIterator<APostProcessVolume> It(World); It; ++It)
|
|
{
|
|
if (It->bUnbound) // Prefer unbound (global) volumes
|
|
{
|
|
return *It;
|
|
}
|
|
}
|
|
|
|
// Return any post-process volume if no unbound one found
|
|
for (TActorIterator<APostProcessVolume> It(World); It; ++It)
|
|
{
|
|
return *It;
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
ADirectionalLight* URalphaParameterBridge::FindDirectionalLight()
|
|
{
|
|
UWorld* World = GetCurrentWorld();
|
|
if (!World) return nullptr;
|
|
|
|
for (TActorIterator<ADirectionalLight> It(World); It; ++It)
|
|
{
|
|
return *It;
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
ASkyLight* URalphaParameterBridge::FindSkyLight()
|
|
{
|
|
UWorld* World = GetCurrentWorld();
|
|
if (!World) return nullptr;
|
|
|
|
for (TActorIterator<ASkyLight> It(World); It; ++It)
|
|
{
|
|
return *It;
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
AExponentialHeightFog* URalphaParameterBridge::FindExponentialHeightFog()
|
|
{
|
|
UWorld* World = GetCurrentWorld();
|
|
if (!World) return nullptr;
|
|
|
|
for (TActorIterator<AExponentialHeightFog> It(World); It; ++It)
|
|
{
|
|
return *It;
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
ACineCameraActor* URalphaParameterBridge::FindCineCamera()
|
|
{
|
|
UWorld* World = GetCurrentWorld();
|
|
if (!World) return nullptr;
|
|
|
|
for (TActorIterator<ACineCameraActor> It(World); It; ++It)
|
|
{
|
|
return *It;
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
FLinearColor URalphaParameterBridge::ParseHexColor(const FString& HexColor)
|
|
{
|
|
FString CleanHex = HexColor;
|
|
CleanHex.RemoveFromStart(TEXT("#"));
|
|
|
|
FColor Color = FColor::FromHex(CleanHex);
|
|
return FLinearColor(Color);
|
|
}
|
|
|
|
FString URalphaParameterBridge::ColorToHex(const FLinearColor& Color)
|
|
{
|
|
FColor SRGBColor = Color.ToFColor(true);
|
|
return FString::Printf(TEXT("#%02X%02X%02X"), SRGBColor.R, SRGBColor.G, SRGBColor.B);
|
|
}
|
|
|
|
TSharedPtr<FJsonObject> URalphaParameterBridge::GetAllParameters()
|
|
{
|
|
TSharedPtr<FJsonObject> Result = MakeShared<FJsonObject>();
|
|
|
|
Result->SetObjectField(TEXT("post_process"), GetPostProcessParameters());
|
|
Result->SetObjectField(TEXT("directional_light"), GetDirectionalLightParameters());
|
|
Result->SetObjectField(TEXT("sky_light"), GetSkyLightParameters());
|
|
Result->SetObjectField(TEXT("fog"), GetFogParameters());
|
|
Result->SetObjectField(TEXT("camera"), GetCameraParameters());
|
|
|
|
return Result;
|
|
}
|
|
|
|
// ============================================================================
|
|
// Post-Process Volume
|
|
// ============================================================================
|
|
|
|
bool URalphaParameterBridge::SetPostProcessParameters(const TSharedPtr<FJsonObject>& Params)
|
|
{
|
|
APostProcessVolume* PPV = FindPostProcessVolume();
|
|
if (!PPV)
|
|
{
|
|
UE_LOG(LogRalphaMCP, Warning, TEXT("No PostProcessVolume found in scene"));
|
|
return false;
|
|
}
|
|
|
|
FPostProcessSettings& Settings = PPV->Settings;
|
|
|
|
// Exposure
|
|
if (Params->HasField(TEXT("exposure_compensation")))
|
|
{
|
|
Settings.bOverride_AutoExposureBias = true;
|
|
Settings.AutoExposureBias = Params->GetNumberField(TEXT("exposure_compensation"));
|
|
}
|
|
|
|
// Color Grading
|
|
if (Params->HasField(TEXT("color_saturation")))
|
|
{
|
|
Settings.bOverride_ColorSaturation = true;
|
|
float Sat = Params->GetNumberField(TEXT("color_saturation"));
|
|
Settings.ColorSaturation = FVector4(Sat, Sat, Sat, 1.0f);
|
|
}
|
|
|
|
if (Params->HasField(TEXT("color_contrast")))
|
|
{
|
|
Settings.bOverride_ColorContrast = true;
|
|
float Contrast = Params->GetNumberField(TEXT("color_contrast"));
|
|
Settings.ColorContrast = FVector4(Contrast, Contrast, Contrast, 1.0f);
|
|
}
|
|
|
|
// White Balance
|
|
if (Params->HasField(TEXT("white_balance_temp")))
|
|
{
|
|
Settings.bOverride_WhiteTemp = true;
|
|
Settings.WhiteTemp = Params->GetNumberField(TEXT("white_balance_temp"));
|
|
}
|
|
|
|
if (Params->HasField(TEXT("white_balance_tint")))
|
|
{
|
|
Settings.bOverride_WhiteTint = true;
|
|
Settings.WhiteTint = Params->GetNumberField(TEXT("white_balance_tint"));
|
|
}
|
|
|
|
// Bloom
|
|
if (Params->HasField(TEXT("bloom_intensity")))
|
|
{
|
|
Settings.bOverride_BloomIntensity = true;
|
|
Settings.BloomIntensity = Params->GetNumberField(TEXT("bloom_intensity"));
|
|
}
|
|
|
|
if (Params->HasField(TEXT("bloom_threshold")))
|
|
{
|
|
Settings.bOverride_BloomThreshold = true;
|
|
Settings.BloomThreshold = Params->GetNumberField(TEXT("bloom_threshold"));
|
|
}
|
|
|
|
// Vignette
|
|
if (Params->HasField(TEXT("vignette_intensity")))
|
|
{
|
|
Settings.bOverride_VignetteIntensity = true;
|
|
Settings.VignetteIntensity = Params->GetNumberField(TEXT("vignette_intensity"));
|
|
}
|
|
|
|
// Film Grain
|
|
if (Params->HasField(TEXT("film_grain_intensity")))
|
|
{
|
|
Settings.bOverride_FilmGrainIntensity = true;
|
|
Settings.FilmGrainIntensity = Params->GetNumberField(TEXT("film_grain_intensity"));
|
|
}
|
|
|
|
// Chromatic Aberration
|
|
if (Params->HasField(TEXT("chromatic_aberration_intensity")))
|
|
{
|
|
Settings.bOverride_SceneFringeIntensity = true;
|
|
Settings.SceneFringeIntensity = Params->GetNumberField(TEXT("chromatic_aberration_intensity"));
|
|
}
|
|
|
|
PPV->MarkPackageDirty();
|
|
return true;
|
|
}
|
|
|
|
TSharedPtr<FJsonObject> URalphaParameterBridge::GetPostProcessParameters()
|
|
{
|
|
TSharedPtr<FJsonObject> Result = MakeShared<FJsonObject>();
|
|
|
|
APostProcessVolume* PPV = FindPostProcessVolume();
|
|
if (!PPV)
|
|
{
|
|
return Result;
|
|
}
|
|
|
|
const FPostProcessSettings& Settings = PPV->Settings;
|
|
|
|
Result->SetNumberField(TEXT("exposure_compensation"), Settings.AutoExposureBias);
|
|
Result->SetNumberField(TEXT("color_saturation"), Settings.ColorSaturation.X);
|
|
Result->SetNumberField(TEXT("color_contrast"), Settings.ColorContrast.X);
|
|
Result->SetNumberField(TEXT("white_balance_temp"), Settings.WhiteTemp);
|
|
Result->SetNumberField(TEXT("white_balance_tint"), Settings.WhiteTint);
|
|
Result->SetNumberField(TEXT("bloom_intensity"), Settings.BloomIntensity);
|
|
Result->SetNumberField(TEXT("bloom_threshold"), Settings.BloomThreshold);
|
|
Result->SetNumberField(TEXT("vignette_intensity"), Settings.VignetteIntensity);
|
|
Result->SetNumberField(TEXT("film_grain_intensity"), Settings.FilmGrainIntensity);
|
|
Result->SetNumberField(TEXT("chromatic_aberration_intensity"), Settings.SceneFringeIntensity);
|
|
|
|
return Result;
|
|
}
|
|
|
|
// ============================================================================
|
|
// Directional Light
|
|
// ============================================================================
|
|
|
|
bool URalphaParameterBridge::SetDirectionalLightParameters(const TSharedPtr<FJsonObject>& Params)
|
|
{
|
|
ADirectionalLight* Light = FindDirectionalLight();
|
|
if (!Light)
|
|
{
|
|
UE_LOG(LogRalphaMCP, Warning, TEXT("No DirectionalLight found in scene"));
|
|
return false;
|
|
}
|
|
|
|
UDirectionalLightComponent* LightComp = Cast<UDirectionalLightComponent>(Light->GetLightComponent());
|
|
if (!LightComp) return false;
|
|
|
|
if (Params->HasField(TEXT("intensity")))
|
|
{
|
|
LightComp->SetIntensity(Params->GetNumberField(TEXT("intensity")));
|
|
}
|
|
|
|
if (Params->HasField(TEXT("color")))
|
|
{
|
|
FLinearColor Color = ParseHexColor(Params->GetStringField(TEXT("color")));
|
|
LightComp->SetLightColor(Color);
|
|
}
|
|
|
|
if (Params->HasField(TEXT("temperature")))
|
|
{
|
|
LightComp->bUseTemperature = true;
|
|
LightComp->SetTemperature(Params->GetNumberField(TEXT("temperature")));
|
|
}
|
|
|
|
if (Params->HasField(TEXT("pitch")) || Params->HasField(TEXT("yaw")))
|
|
{
|
|
FRotator CurrentRot = Light->GetActorRotation();
|
|
if (Params->HasField(TEXT("pitch")))
|
|
{
|
|
CurrentRot.Pitch = Params->GetNumberField(TEXT("pitch"));
|
|
}
|
|
if (Params->HasField(TEXT("yaw")))
|
|
{
|
|
CurrentRot.Yaw = Params->GetNumberField(TEXT("yaw"));
|
|
}
|
|
Light->SetActorRotation(CurrentRot);
|
|
}
|
|
|
|
if (Params->HasField(TEXT("source_angle")))
|
|
{
|
|
LightComp->LightSourceAngle = Params->GetNumberField(TEXT("source_angle"));
|
|
}
|
|
|
|
if (Params->HasField(TEXT("source_soft_angle")))
|
|
{
|
|
LightComp->LightSourceSoftAngle = Params->GetNumberField(TEXT("source_soft_angle"));
|
|
}
|
|
|
|
LightComp->MarkRenderStateDirty();
|
|
|
|
// Refresh any BP_Sky_Sphere to update based on new sun position
|
|
RefreshSkySphere();
|
|
|
|
return true;
|
|
}
|
|
|
|
void URalphaParameterBridge::RefreshSkySphere()
|
|
{
|
|
UWorld* World = GetCurrentWorld();
|
|
if (!World) return;
|
|
|
|
ADirectionalLight* DirectionalLight = FindDirectionalLight();
|
|
|
|
// Find BP_Sky_Sphere and refresh it
|
|
for (TActorIterator<AActor> It(World); It; ++It)
|
|
{
|
|
FString ClassName = It->GetClass()->GetName();
|
|
if (ClassName.Contains(TEXT("BP_Sky")))
|
|
{
|
|
AActor* SkySphere = *It;
|
|
|
|
// Ensure directional light is linked
|
|
if (DirectionalLight)
|
|
{
|
|
FProperty* DirLightProp = SkySphere->GetClass()->FindPropertyByName(TEXT("Directional light actor"));
|
|
if (!DirLightProp)
|
|
{
|
|
DirLightProp = SkySphere->GetClass()->FindPropertyByName(TEXT("DirectionalLightActor"));
|
|
}
|
|
if (DirLightProp)
|
|
{
|
|
FObjectProperty* ObjProp = CastField<FObjectProperty>(DirLightProp);
|
|
if (ObjProp)
|
|
{
|
|
ObjProp->SetObjectPropertyValue_InContainer(SkySphere, DirectionalLight);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Call RefreshMaterial function
|
|
UFunction* RefreshFunc = SkySphere->FindFunction(TEXT("RefreshMaterial"));
|
|
if (RefreshFunc)
|
|
{
|
|
SkySphere->ProcessEvent(RefreshFunc, nullptr);
|
|
UE_LOG(LogRalphaMCP, Log, TEXT("Refreshed BP_Sky_Sphere material"));
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
TSharedPtr<FJsonObject> URalphaParameterBridge::GetDirectionalLightParameters()
|
|
{
|
|
TSharedPtr<FJsonObject> Result = MakeShared<FJsonObject>();
|
|
|
|
ADirectionalLight* Light = FindDirectionalLight();
|
|
if (!Light) return Result;
|
|
|
|
UDirectionalLightComponent* LightComp = Cast<UDirectionalLightComponent>(Light->GetLightComponent());
|
|
if (!LightComp) return Result;
|
|
|
|
Result->SetNumberField(TEXT("intensity"), LightComp->Intensity);
|
|
Result->SetStringField(TEXT("color"), ColorToHex(LightComp->GetLightColor()));
|
|
Result->SetNumberField(TEXT("temperature"), LightComp->Temperature);
|
|
Result->SetNumberField(TEXT("pitch"), Light->GetActorRotation().Pitch);
|
|
Result->SetNumberField(TEXT("yaw"), Light->GetActorRotation().Yaw);
|
|
Result->SetNumberField(TEXT("source_angle"), LightComp->LightSourceAngle);
|
|
Result->SetNumberField(TEXT("source_soft_angle"), LightComp->LightSourceSoftAngle);
|
|
|
|
return Result;
|
|
}
|
|
|
|
// ============================================================================
|
|
// Sky Light
|
|
// ============================================================================
|
|
|
|
bool URalphaParameterBridge::SetSkyLightParameters(const TSharedPtr<FJsonObject>& Params)
|
|
{
|
|
ASkyLight* Light = FindSkyLight();
|
|
if (!Light)
|
|
{
|
|
UE_LOG(LogRalphaMCP, Warning, TEXT("No SkyLight found in scene"));
|
|
return false;
|
|
}
|
|
|
|
USkyLightComponent* LightComp = Light->GetLightComponent();
|
|
if (!LightComp) return false;
|
|
|
|
if (Params->HasField(TEXT("intensity")))
|
|
{
|
|
LightComp->SetIntensity(Params->GetNumberField(TEXT("intensity")));
|
|
}
|
|
|
|
if (Params->HasField(TEXT("color")))
|
|
{
|
|
FLinearColor Color = ParseHexColor(Params->GetStringField(TEXT("color")));
|
|
LightComp->SetLightColor(Color);
|
|
}
|
|
|
|
LightComp->MarkRenderStateDirty();
|
|
return true;
|
|
}
|
|
|
|
TSharedPtr<FJsonObject> URalphaParameterBridge::GetSkyLightParameters()
|
|
{
|
|
TSharedPtr<FJsonObject> Result = MakeShared<FJsonObject>();
|
|
|
|
ASkyLight* Light = FindSkyLight();
|
|
if (!Light) return Result;
|
|
|
|
USkyLightComponent* LightComp = Light->GetLightComponent();
|
|
if (!LightComp) return Result;
|
|
|
|
Result->SetNumberField(TEXT("intensity"), LightComp->Intensity);
|
|
Result->SetStringField(TEXT("color"), ColorToHex(LightComp->GetLightColor()));
|
|
|
|
return Result;
|
|
}
|
|
|
|
// ============================================================================
|
|
// Exponential Height Fog
|
|
// ============================================================================
|
|
|
|
bool URalphaParameterBridge::SetFogParameters(const TSharedPtr<FJsonObject>& Params)
|
|
{
|
|
AExponentialHeightFog* Fog = FindExponentialHeightFog();
|
|
if (!Fog)
|
|
{
|
|
UE_LOG(LogRalphaMCP, Warning, TEXT("No ExponentialHeightFog found in scene"));
|
|
return false;
|
|
}
|
|
|
|
UExponentialHeightFogComponent* FogComp = Fog->GetComponent();
|
|
if (!FogComp) return false;
|
|
|
|
if (Params->HasField(TEXT("enabled")))
|
|
{
|
|
FogComp->SetVisibility(Params->GetBoolField(TEXT("enabled")));
|
|
}
|
|
|
|
if (Params->HasField(TEXT("fog_density")))
|
|
{
|
|
FogComp->SetFogDensity(Params->GetNumberField(TEXT("fog_density")));
|
|
}
|
|
|
|
if (Params->HasField(TEXT("fog_height_falloff")))
|
|
{
|
|
FogComp->SetFogHeightFalloff(Params->GetNumberField(TEXT("fog_height_falloff")));
|
|
}
|
|
|
|
if (Params->HasField(TEXT("fog_inscattering_color")))
|
|
{
|
|
FLinearColor Color = ParseHexColor(Params->GetStringField(TEXT("fog_inscattering_color")));
|
|
FogComp->SetFogInscatteringColor(Color);
|
|
}
|
|
|
|
if (Params->HasField(TEXT("fog_max_opacity")))
|
|
{
|
|
FogComp->SetFogMaxOpacity(Params->GetNumberField(TEXT("fog_max_opacity")));
|
|
}
|
|
|
|
if (Params->HasField(TEXT("start_distance")))
|
|
{
|
|
FogComp->SetStartDistance(Params->GetNumberField(TEXT("start_distance")));
|
|
}
|
|
|
|
if (Params->HasField(TEXT("volumetric_fog")))
|
|
{
|
|
FogComp->SetVolumetricFog(Params->GetBoolField(TEXT("volumetric_fog")));
|
|
}
|
|
|
|
if (Params->HasField(TEXT("volumetric_fog_scattering_distribution")))
|
|
{
|
|
FogComp->VolumetricFogScatteringDistribution = Params->GetNumberField(TEXT("volumetric_fog_scattering_distribution"));
|
|
}
|
|
|
|
FogComp->MarkRenderStateDirty();
|
|
return true;
|
|
}
|
|
|
|
TSharedPtr<FJsonObject> URalphaParameterBridge::GetFogParameters()
|
|
{
|
|
TSharedPtr<FJsonObject> Result = MakeShared<FJsonObject>();
|
|
|
|
AExponentialHeightFog* Fog = FindExponentialHeightFog();
|
|
if (!Fog) return Result;
|
|
|
|
UExponentialHeightFogComponent* FogComp = Fog->GetComponent();
|
|
if (!FogComp) return Result;
|
|
|
|
Result->SetBoolField(TEXT("enabled"), FogComp->IsVisible());
|
|
Result->SetNumberField(TEXT("fog_density"), FogComp->FogDensity);
|
|
Result->SetNumberField(TEXT("fog_height_falloff"), FogComp->FogHeightFalloff);
|
|
Result->SetNumberField(TEXT("fog_max_opacity"), FogComp->FogMaxOpacity);
|
|
Result->SetNumberField(TEXT("start_distance"), FogComp->StartDistance);
|
|
Result->SetBoolField(TEXT("volumetric_fog"), FogComp->bEnableVolumetricFog);
|
|
Result->SetNumberField(TEXT("volumetric_fog_scattering_distribution"), FogComp->VolumetricFogScatteringDistribution);
|
|
|
|
return Result;
|
|
}
|
|
|
|
// ============================================================================
|
|
// Camera
|
|
// ============================================================================
|
|
|
|
bool URalphaParameterBridge::SetCameraParameters(const TSharedPtr<FJsonObject>& Params)
|
|
{
|
|
ACineCameraActor* Camera = FindCineCamera();
|
|
if (!Camera)
|
|
{
|
|
UE_LOG(LogRalphaMCP, Warning, TEXT("No CineCameraActor found in scene"));
|
|
return false;
|
|
}
|
|
|
|
UCineCameraComponent* CameraComp = Camera->GetCineCameraComponent();
|
|
if (!CameraComp) return false;
|
|
|
|
// Camera location
|
|
const TSharedPtr<FJsonObject>* LocationObj;
|
|
if (Params->TryGetObjectField(TEXT("location"), LocationObj))
|
|
{
|
|
FVector Location = Camera->GetActorLocation();
|
|
if ((*LocationObj)->HasField(TEXT("x"))) Location.X = (*LocationObj)->GetNumberField(TEXT("x"));
|
|
if ((*LocationObj)->HasField(TEXT("y"))) Location.Y = (*LocationObj)->GetNumberField(TEXT("y"));
|
|
if ((*LocationObj)->HasField(TEXT("z"))) Location.Z = (*LocationObj)->GetNumberField(TEXT("z"));
|
|
Camera->SetActorLocation(Location);
|
|
}
|
|
|
|
// Camera rotation
|
|
const TSharedPtr<FJsonObject>* RotationObj;
|
|
if (Params->TryGetObjectField(TEXT("rotation"), RotationObj))
|
|
{
|
|
FRotator Rotation = Camera->GetActorRotation();
|
|
if ((*RotationObj)->HasField(TEXT("pitch"))) Rotation.Pitch = (*RotationObj)->GetNumberField(TEXT("pitch"));
|
|
if ((*RotationObj)->HasField(TEXT("yaw"))) Rotation.Yaw = (*RotationObj)->GetNumberField(TEXT("yaw"));
|
|
if ((*RotationObj)->HasField(TEXT("roll"))) Rotation.Roll = (*RotationObj)->GetNumberField(TEXT("roll"));
|
|
Camera->SetActorRotation(Rotation);
|
|
}
|
|
|
|
if (Params->HasField(TEXT("focal_length")))
|
|
{
|
|
CameraComp->SetCurrentFocalLength(Params->GetNumberField(TEXT("focal_length")));
|
|
}
|
|
|
|
if (Params->HasField(TEXT("aperture")))
|
|
{
|
|
CameraComp->CurrentAperture = Params->GetNumberField(TEXT("aperture"));
|
|
}
|
|
|
|
if (Params->HasField(TEXT("focus_distance")))
|
|
{
|
|
CameraComp->FocusSettings.ManualFocusDistance = Params->GetNumberField(TEXT("focus_distance"));
|
|
}
|
|
|
|
if (Params->HasField(TEXT("dof_enabled")))
|
|
{
|
|
CameraComp->PostProcessSettings.bOverride_DepthOfFieldFstop = Params->GetBoolField(TEXT("dof_enabled"));
|
|
}
|
|
|
|
if (Params->HasField(TEXT("motion_blur_amount")))
|
|
{
|
|
CameraComp->PostProcessSettings.bOverride_MotionBlurAmount = true;
|
|
CameraComp->PostProcessSettings.MotionBlurAmount = Params->GetNumberField(TEXT("motion_blur_amount"));
|
|
}
|
|
|
|
CameraComp->MarkRenderStateDirty();
|
|
return true;
|
|
}
|
|
|
|
TSharedPtr<FJsonObject> URalphaParameterBridge::GetCameraParameters()
|
|
{
|
|
TSharedPtr<FJsonObject> Result = MakeShared<FJsonObject>();
|
|
|
|
ACineCameraActor* Camera = FindCineCamera();
|
|
if (!Camera) return Result;
|
|
|
|
UCineCameraComponent* CameraComp = Camera->GetCineCameraComponent();
|
|
if (!CameraComp) return Result;
|
|
|
|
Result->SetNumberField(TEXT("focal_length"), CameraComp->CurrentFocalLength);
|
|
Result->SetNumberField(TEXT("aperture"), CameraComp->CurrentAperture);
|
|
Result->SetNumberField(TEXT("focus_distance"), CameraComp->FocusSettings.ManualFocusDistance);
|
|
Result->SetNumberField(TEXT("motion_blur_amount"), CameraComp->PostProcessSettings.MotionBlurAmount);
|
|
|
|
return Result;
|
|
}
|
|
|
|
// ============================================================================
|
|
// Render Quality
|
|
// ============================================================================
|
|
|
|
bool URalphaParameterBridge::SetRenderQuality(const TSharedPtr<FJsonObject>& Settings)
|
|
{
|
|
// This would typically use console commands or project settings
|
|
// For now, we'll log what we would set
|
|
|
|
if (Settings->HasField(TEXT("lumen_quality")))
|
|
{
|
|
float Quality = Settings->GetNumberField(TEXT("lumen_quality"));
|
|
UE_LOG(LogRalphaMCP, Log, TEXT("Would set Lumen quality to: %f"), Quality);
|
|
// r.Lumen.ScreenTracing.MaxIterations, etc.
|
|
}
|
|
|
|
if (Settings->HasField(TEXT("shadow_quality")))
|
|
{
|
|
FString Quality = Settings->GetStringField(TEXT("shadow_quality"));
|
|
UE_LOG(LogRalphaMCP, Log, TEXT("Would set shadow quality to: %s"), *Quality);
|
|
// sg.ShadowQuality
|
|
}
|
|
|
|
if (Settings->HasField(TEXT("resolution_scale")))
|
|
{
|
|
float Scale = Settings->GetNumberField(TEXT("resolution_scale"));
|
|
UE_LOG(LogRalphaMCP, Log, TEXT("Would set resolution scale to: %f"), Scale);
|
|
// r.ScreenPercentage
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
// ============================================================================
|
|
// Asset Search
|
|
// ============================================================================
|
|
|
|
TArray<TSharedPtr<FJsonValue>> URalphaParameterBridge::SearchAssets(const FString& Query, const FString& AssetType, int32 MaxResults)
|
|
{
|
|
TArray<TSharedPtr<FJsonValue>> Results;
|
|
|
|
FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked<FAssetRegistryModule>("AssetRegistry");
|
|
IAssetRegistry& AssetRegistry = AssetRegistryModule.Get();
|
|
|
|
FARFilter Filter;
|
|
Filter.bRecursivePaths = true;
|
|
Filter.PackagePaths.Add(TEXT("/Game"));
|
|
|
|
if (AssetType == TEXT("StaticMesh"))
|
|
{
|
|
Filter.ClassPaths.Add(UStaticMesh::StaticClass()->GetClassPathName());
|
|
}
|
|
// Add more type filters as needed
|
|
|
|
TArray<FAssetData> AssetList;
|
|
AssetRegistry.GetAssets(Filter, AssetList);
|
|
|
|
int32 Count = 0;
|
|
for (const FAssetData& Asset : AssetList)
|
|
{
|
|
if (Count >= MaxResults) break;
|
|
|
|
FString AssetName = Asset.AssetName.ToString();
|
|
if (AssetName.Contains(Query, ESearchCase::IgnoreCase))
|
|
{
|
|
TSharedPtr<FJsonObject> AssetObj = MakeShared<FJsonObject>();
|
|
AssetObj->SetStringField(TEXT("name"), AssetName);
|
|
AssetObj->SetStringField(TEXT("asset_path"), Asset.GetObjectPathString());
|
|
AssetObj->SetStringField(TEXT("class"), Asset.AssetClassPath.GetAssetName().ToString());
|
|
|
|
Results.Add(MakeShared<FJsonValueObject>(AssetObj));
|
|
Count++;
|
|
}
|
|
}
|
|
|
|
return Results;
|
|
}
|
|
|
|
TArray<TSharedPtr<FJsonValue>> URalphaParameterBridge::ListAssets(const FString& FolderPath, const FString& AssetType, bool bRecursive)
|
|
{
|
|
TArray<TSharedPtr<FJsonValue>> Results;
|
|
|
|
FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked<FAssetRegistryModule>("AssetRegistry");
|
|
IAssetRegistry& AssetRegistry = AssetRegistryModule.Get();
|
|
|
|
FARFilter Filter;
|
|
Filter.bRecursivePaths = bRecursive;
|
|
Filter.PackagePaths.Add(*FolderPath);
|
|
|
|
TArray<FAssetData> AssetList;
|
|
AssetRegistry.GetAssets(Filter, AssetList);
|
|
|
|
for (const FAssetData& Asset : AssetList)
|
|
{
|
|
TSharedPtr<FJsonObject> AssetObj = MakeShared<FJsonObject>();
|
|
AssetObj->SetStringField(TEXT("name"), Asset.AssetName.ToString());
|
|
AssetObj->SetStringField(TEXT("asset_path"), Asset.GetObjectPathString());
|
|
AssetObj->SetStringField(TEXT("class"), Asset.AssetClassPath.GetAssetName().ToString());
|
|
|
|
Results.Add(MakeShared<FJsonValueObject>(AssetObj));
|
|
}
|
|
|
|
return Results;
|
|
}
|
|
|
|
// ============================================================================
|
|
// Actor Management
|
|
// ============================================================================
|
|
|
|
bool URalphaParameterBridge::SpawnActor(const FString& AssetPath, const FVector& Location, const FRotator& Rotation, float Scale, const FString& ActorLabel, FString& OutActorId)
|
|
{
|
|
UWorld* World = GetCurrentWorld();
|
|
if (!World) return false;
|
|
|
|
// Load the asset
|
|
UObject* LoadedAsset = StaticLoadObject(UStaticMesh::StaticClass(), nullptr, *AssetPath);
|
|
UStaticMesh* StaticMesh = Cast<UStaticMesh>(LoadedAsset);
|
|
|
|
if (!StaticMesh)
|
|
{
|
|
UE_LOG(LogRalphaMCP, Warning, TEXT("Failed to load static mesh: %s"), *AssetPath);
|
|
return false;
|
|
}
|
|
|
|
// Spawn the actor
|
|
FActorSpawnParameters SpawnParams;
|
|
SpawnParams.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AlwaysSpawn;
|
|
|
|
AStaticMeshActor* NewActor = World->SpawnActor<AStaticMeshActor>(Location, Rotation, SpawnParams);
|
|
if (!NewActor)
|
|
{
|
|
UE_LOG(LogRalphaMCP, Warning, TEXT("Failed to spawn actor"));
|
|
return false;
|
|
}
|
|
|
|
NewActor->GetStaticMeshComponent()->SetStaticMesh(StaticMesh);
|
|
NewActor->SetActorScale3D(FVector(Scale));
|
|
|
|
#if WITH_EDITOR
|
|
if (!ActorLabel.IsEmpty())
|
|
{
|
|
NewActor->SetActorLabel(ActorLabel);
|
|
}
|
|
#endif
|
|
|
|
OutActorId = NewActor->GetName();
|
|
return true;
|
|
}
|
|
|
|
bool URalphaParameterBridge::DeleteActor(const FString& ActorId)
|
|
{
|
|
UWorld* World = GetCurrentWorld();
|
|
if (!World) return false;
|
|
|
|
for (TActorIterator<AActor> It(World); It; ++It)
|
|
{
|
|
if (It->GetName() == ActorId)
|
|
{
|
|
World->DestroyActor(*It);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
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)
|
|
{
|
|
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("SetActorMaterial: Actor not found: %s"), *ActorId);
|
|
return false;
|
|
}
|
|
|
|
// Load the material
|
|
UMaterialInterface* Material = Cast<UMaterialInterface>(StaticLoadObject(UMaterialInterface::StaticClass(), nullptr, *MaterialPath));
|
|
if (!Material)
|
|
{
|
|
UE_LOG(LogRalphaMCP, Warning, TEXT("SetActorMaterial: Failed to load material: %s"), *MaterialPath);
|
|
return false;
|
|
}
|
|
|
|
// Try to find a mesh component to apply the material to
|
|
AStaticMeshActor* MeshActor = Cast<AStaticMeshActor>(TargetActor);
|
|
if (MeshActor && MeshActor->GetStaticMeshComponent())
|
|
{
|
|
MeshActor->GetStaticMeshComponent()->SetMaterial(MaterialIndex, Material);
|
|
UE_LOG(LogRalphaMCP, Log, TEXT("SetActorMaterial: Applied material %s to %s"), *MaterialPath, *ActorId);
|
|
return true;
|
|
}
|
|
|
|
// Try generic primitive component
|
|
UPrimitiveComponent* PrimComp = TargetActor->FindComponentByClass<UPrimitiveComponent>();
|
|
if (PrimComp)
|
|
{
|
|
PrimComp->SetMaterial(MaterialIndex, Material);
|
|
UE_LOG(LogRalphaMCP, Log, TEXT("SetActorMaterial: Applied material %s to %s (primitive)"), *MaterialPath, *ActorId);
|
|
return true;
|
|
}
|
|
|
|
UE_LOG(LogRalphaMCP, Warning, TEXT("SetActorMaterial: No suitable component found on %s"), *ActorId);
|
|
return false;
|
|
}
|
|
|
|
bool URalphaParameterBridge::SetActorSimpleMaterial(const FString& ActorId, const FLinearColor& BaseColor, float Metallic, float Roughness, float Opacity)
|
|
{
|
|
UWorld* World = GetCurrentWorld();
|
|
if (!World) return false;
|
|
|
|
// Find the actor
|
|
AActor* TargetActor = nullptr;
|
|
for (TActorIterator<AActor> It(World); It; ++It)
|
|
{
|
|
if (It->GetName() == ActorId)
|
|
{
|
|
TargetActor = *It;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!TargetActor)
|
|
{
|
|
UE_LOG(LogRalphaMCP, Warning, TEXT("SetActorSimpleMaterial: Actor not found: %s"), *ActorId);
|
|
return false;
|
|
}
|
|
|
|
// Load the basic material from engine content - M_Basic_Wall is a simple material that supports our parameters
|
|
UMaterialInterface* BaseMaterial = Cast<UMaterialInterface>(StaticLoadObject(UMaterialInterface::StaticClass(), nullptr, TEXT("/Engine/BasicShapes/BasicShapeMaterial.BasicShapeMaterial")));
|
|
if (!BaseMaterial)
|
|
{
|
|
UE_LOG(LogRalphaMCP, Warning, TEXT("SetActorSimpleMaterial: Failed to load base material"));
|
|
return false;
|
|
}
|
|
|
|
// Create a dynamic material instance
|
|
UMaterialInstanceDynamic* DynMaterial = UMaterialInstanceDynamic::Create(BaseMaterial, TargetActor);
|
|
if (!DynMaterial)
|
|
{
|
|
UE_LOG(LogRalphaMCP, Warning, TEXT("SetActorSimpleMaterial: Failed to create dynamic material"));
|
|
return false;
|
|
}
|
|
|
|
// Set material parameters - BasicShapeMaterial uses specific parameter names
|
|
// Try common parameter names for base color
|
|
DynMaterial->SetVectorParameterValue(TEXT("Color"), BaseColor);
|
|
DynMaterial->SetVectorParameterValue(TEXT("BaseColor"), BaseColor);
|
|
DynMaterial->SetVectorParameterValue(TEXT("Base Color"), BaseColor);
|
|
|
|
// Try common scalar parameters
|
|
DynMaterial->SetScalarParameterValue(TEXT("Metallic"), Metallic);
|
|
DynMaterial->SetScalarParameterValue(TEXT("Roughness"), Roughness);
|
|
DynMaterial->SetScalarParameterValue(TEXT("Opacity"), Opacity);
|
|
|
|
// Apply to the actor's mesh component
|
|
AStaticMeshActor* MeshActor = Cast<AStaticMeshActor>(TargetActor);
|
|
if (MeshActor && MeshActor->GetStaticMeshComponent())
|
|
{
|
|
MeshActor->GetStaticMeshComponent()->SetMaterial(0, DynMaterial);
|
|
UE_LOG(LogRalphaMCP, Log, TEXT("SetActorSimpleMaterial: Applied dynamic material to %s (Color: R=%.2f G=%.2f B=%.2f, Metallic=%.2f, Roughness=%.2f)"),
|
|
*ActorId, BaseColor.R, BaseColor.G, BaseColor.B, Metallic, Roughness);
|
|
return true;
|
|
}
|
|
|
|
// Try generic primitive component
|
|
UPrimitiveComponent* PrimComp = TargetActor->FindComponentByClass<UPrimitiveComponent>();
|
|
if (PrimComp)
|
|
{
|
|
PrimComp->SetMaterial(0, DynMaterial);
|
|
UE_LOG(LogRalphaMCP, Log, TEXT("SetActorSimpleMaterial: Applied dynamic material to %s (primitive)"), *ActorId);
|
|
return true;
|
|
}
|
|
|
|
UE_LOG(LogRalphaMCP, Warning, TEXT("SetActorSimpleMaterial: No suitable component found on %s"), *ActorId);
|
|
return false;
|
|
}
|
|
|
|
bool URalphaParameterBridge::SetActorEmissiveMaterial(const FString& ActorId, const FLinearColor& EmissiveColor, float EmissiveIntensity)
|
|
{
|
|
UWorld* World = GetCurrentWorld();
|
|
if (!World) return false;
|
|
|
|
// Find the actor
|
|
AActor* TargetActor = nullptr;
|
|
for (TActorIterator<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>> Results;
|
|
|
|
UWorld* World = GetCurrentWorld();
|
|
if (!World) return Results;
|
|
|
|
for (TActorIterator<AActor> It(World); It; ++It)
|
|
{
|
|
AActor* Actor = *It;
|
|
|
|
// Apply filters
|
|
if (!ClassFilter.IsEmpty() && !Actor->GetClass()->GetName().Contains(ClassFilter))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
#if WITH_EDITOR
|
|
if (!NameFilter.IsEmpty() && !Actor->GetActorLabel().Contains(NameFilter))
|
|
{
|
|
continue;
|
|
}
|
|
#else
|
|
if (!NameFilter.IsEmpty() && !Actor->GetName().Contains(NameFilter))
|
|
{
|
|
continue;
|
|
}
|
|
#endif
|
|
|
|
TSharedPtr<FJsonObject> ActorObj = MakeShared<FJsonObject>();
|
|
ActorObj->SetStringField(TEXT("actor_id"), Actor->GetName());
|
|
#if WITH_EDITOR
|
|
ActorObj->SetStringField(TEXT("label"), Actor->GetActorLabel());
|
|
#else
|
|
ActorObj->SetStringField(TEXT("label"), Actor->GetName());
|
|
#endif
|
|
ActorObj->SetStringField(TEXT("class"), Actor->GetClass()->GetName());
|
|
|
|
if (bIncludeTransforms)
|
|
{
|
|
FVector Location = Actor->GetActorLocation();
|
|
FRotator Rotation = Actor->GetActorRotation();
|
|
FVector Scale = Actor->GetActorScale3D();
|
|
|
|
TSharedPtr<FJsonObject> TransformObj = MakeShared<FJsonObject>();
|
|
|
|
TSharedPtr<FJsonObject> LocObj = MakeShared<FJsonObject>();
|
|
LocObj->SetNumberField(TEXT("x"), Location.X);
|
|
LocObj->SetNumberField(TEXT("y"), Location.Y);
|
|
LocObj->SetNumberField(TEXT("z"), Location.Z);
|
|
TransformObj->SetObjectField(TEXT("location"), LocObj);
|
|
|
|
TSharedPtr<FJsonObject> RotObj = MakeShared<FJsonObject>();
|
|
RotObj->SetNumberField(TEXT("pitch"), Rotation.Pitch);
|
|
RotObj->SetNumberField(TEXT("yaw"), Rotation.Yaw);
|
|
RotObj->SetNumberField(TEXT("roll"), Rotation.Roll);
|
|
TransformObj->SetObjectField(TEXT("rotation"), RotObj);
|
|
|
|
TSharedPtr<FJsonObject> ScaleObj = MakeShared<FJsonObject>();
|
|
ScaleObj->SetNumberField(TEXT("x"), Scale.X);
|
|
ScaleObj->SetNumberField(TEXT("y"), Scale.Y);
|
|
ScaleObj->SetNumberField(TEXT("z"), Scale.Z);
|
|
TransformObj->SetObjectField(TEXT("scale"), ScaleObj);
|
|
|
|
ActorObj->SetObjectField(TEXT("transform"), TransformObj);
|
|
}
|
|
|
|
Results.Add(MakeShared<FJsonValueObject>(ActorObj));
|
|
}
|
|
|
|
return Results;
|
|
}
|
|
|
|
bool URalphaParameterBridge::SetActorTransform(const FString& ActorId, const FVector* Location, const FRotator* Rotation, const float* Scale, bool bRelative)
|
|
{
|
|
UWorld* World = GetCurrentWorld();
|
|
if (!World) return false;
|
|
|
|
for (TActorIterator<AActor> It(World); It; ++It)
|
|
{
|
|
if (It->GetName() == ActorId)
|
|
{
|
|
AActor* Actor = *It;
|
|
|
|
if (Location)
|
|
{
|
|
if (bRelative)
|
|
{
|
|
Actor->SetActorLocation(Actor->GetActorLocation() + *Location);
|
|
}
|
|
else
|
|
{
|
|
Actor->SetActorLocation(*Location);
|
|
}
|
|
}
|
|
|
|
if (Rotation)
|
|
{
|
|
if (bRelative)
|
|
{
|
|
Actor->SetActorRotation(Actor->GetActorRotation() + *Rotation);
|
|
}
|
|
else
|
|
{
|
|
Actor->SetActorRotation(*Rotation);
|
|
}
|
|
}
|
|
|
|
if (Scale)
|
|
{
|
|
Actor->SetActorScale3D(FVector(*Scale));
|
|
}
|
|
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
TSharedPtr<FJsonObject> URalphaParameterBridge::GetActorDetails(const FString& ActorId, bool bIncludeComponents, bool bIncludeMaterials)
|
|
{
|
|
TSharedPtr<FJsonObject> Result = MakeShared<FJsonObject>();
|
|
|
|
UWorld* World = GetCurrentWorld();
|
|
if (!World) return Result;
|
|
|
|
for (TActorIterator<AActor> It(World); It; ++It)
|
|
{
|
|
if (It->GetName() == ActorId)
|
|
{
|
|
AActor* Actor = *It;
|
|
|
|
Result->SetStringField(TEXT("actor_id"), Actor->GetName());
|
|
#if WITH_EDITOR
|
|
Result->SetStringField(TEXT("label"), Actor->GetActorLabel());
|
|
#else
|
|
Result->SetStringField(TEXT("label"), Actor->GetName());
|
|
#endif
|
|
Result->SetStringField(TEXT("class"), Actor->GetClass()->GetName());
|
|
|
|
// Transform
|
|
FVector Location = Actor->GetActorLocation();
|
|
FRotator Rotation = Actor->GetActorRotation();
|
|
FVector Scale = Actor->GetActorScale3D();
|
|
|
|
TSharedPtr<FJsonObject> TransformObj = MakeShared<FJsonObject>();
|
|
TSharedPtr<FJsonObject> LocObj = MakeShared<FJsonObject>();
|
|
LocObj->SetNumberField(TEXT("x"), Location.X);
|
|
LocObj->SetNumberField(TEXT("y"), Location.Y);
|
|
LocObj->SetNumberField(TEXT("z"), Location.Z);
|
|
TransformObj->SetObjectField(TEXT("location"), LocObj);
|
|
|
|
TSharedPtr<FJsonObject> RotObj = MakeShared<FJsonObject>();
|
|
RotObj->SetNumberField(TEXT("pitch"), Rotation.Pitch);
|
|
RotObj->SetNumberField(TEXT("yaw"), Rotation.Yaw);
|
|
RotObj->SetNumberField(TEXT("roll"), Rotation.Roll);
|
|
TransformObj->SetObjectField(TEXT("rotation"), RotObj);
|
|
|
|
Result->SetObjectField(TEXT("transform"), TransformObj);
|
|
|
|
if (bIncludeComponents)
|
|
{
|
|
TArray<TSharedPtr<FJsonValue>> ComponentsArray;
|
|
TArray<UActorComponent*> Components;
|
|
Actor->GetComponents(Components);
|
|
|
|
for (UActorComponent* Component : Components)
|
|
{
|
|
TSharedPtr<FJsonObject> CompObj = MakeShared<FJsonObject>();
|
|
CompObj->SetStringField(TEXT("name"), Component->GetName());
|
|
CompObj->SetStringField(TEXT("class"), Component->GetClass()->GetName());
|
|
ComponentsArray.Add(MakeShared<FJsonValueObject>(CompObj));
|
|
}
|
|
|
|
Result->SetArrayField(TEXT("components"), ComponentsArray);
|
|
}
|
|
|
|
return Result;
|
|
}
|
|
}
|
|
|
|
return Result;
|
|
}
|
|
|
|
// ============================================================================
|
|
// Scene Setup
|
|
// ============================================================================
|
|
|
|
TSharedPtr<FJsonObject> URalphaParameterBridge::SetupScene()
|
|
{
|
|
TSharedPtr<FJsonObject> Result = MakeShared<FJsonObject>();
|
|
TArray<FString> CreatedActors;
|
|
TArray<FString> ExistingActors;
|
|
|
|
UWorld* World = GetCurrentWorld();
|
|
if (!World)
|
|
{
|
|
Result->SetBoolField(TEXT("success"), false);
|
|
Result->SetStringField(TEXT("error"), TEXT("No world available"));
|
|
return Result;
|
|
}
|
|
|
|
// Check/Create PostProcessVolume
|
|
if (!FindPostProcessVolume())
|
|
{
|
|
FActorSpawnParameters SpawnParams;
|
|
SpawnParams.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AlwaysSpawn;
|
|
APostProcessVolume* PPV = World->SpawnActor<APostProcessVolume>(FVector::ZeroVector, FRotator::ZeroRotator, SpawnParams);
|
|
if (PPV)
|
|
{
|
|
PPV->bUnbound = true;
|
|
PPV->Settings.bOverride_AutoExposureBias = true;
|
|
PPV->Settings.AutoExposureBias = 0.0f;
|
|
#if WITH_EDITOR
|
|
PPV->SetActorLabel(TEXT("Ralpha_PostProcess"));
|
|
#endif
|
|
CreatedActors.Add(TEXT("PostProcessVolume"));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
ExistingActors.Add(TEXT("PostProcessVolume"));
|
|
}
|
|
|
|
// Check/Create DirectionalLight
|
|
ADirectionalLight* Sun = FindDirectionalLight();
|
|
if (!Sun)
|
|
{
|
|
FActorSpawnParameters SpawnParams;
|
|
SpawnParams.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AlwaysSpawn;
|
|
Sun = World->SpawnActor<ADirectionalLight>(FVector::ZeroVector, FRotator(-45.0f, 0.0f, 0.0f), SpawnParams);
|
|
if (Sun)
|
|
{
|
|
#if WITH_EDITOR
|
|
Sun->SetActorLabel(TEXT("Ralpha_Sun"));
|
|
#endif
|
|
CreatedActors.Add(TEXT("DirectionalLight"));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
ExistingActors.Add(TEXT("DirectionalLight"));
|
|
}
|
|
|
|
// Configure DirectionalLight as atmosphere sun for visible sun disc
|
|
if (Sun)
|
|
{
|
|
UDirectionalLightComponent* LightComp = Cast<UDirectionalLightComponent>(Sun->GetLightComponent());
|
|
if (LightComp)
|
|
{
|
|
LightComp->SetAtmosphereSunLight(true);
|
|
LightComp->SetAtmosphereSunLightIndex(0);
|
|
}
|
|
}
|
|
|
|
// Check/Create SkyLight
|
|
if (!FindSkyLight())
|
|
{
|
|
FActorSpawnParameters SpawnParams;
|
|
SpawnParams.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AlwaysSpawn;
|
|
ASkyLight* Light = World->SpawnActor<ASkyLight>(FVector(0.0f, 0.0f, 1000.0f), FRotator::ZeroRotator, SpawnParams);
|
|
if (Light)
|
|
{
|
|
#if WITH_EDITOR
|
|
Light->SetActorLabel(TEXT("Ralpha_SkyLight"));
|
|
#endif
|
|
CreatedActors.Add(TEXT("SkyLight"));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
ExistingActors.Add(TEXT("SkyLight"));
|
|
}
|
|
|
|
// Check/Create ExponentialHeightFog
|
|
if (!FindExponentialHeightFog())
|
|
{
|
|
FActorSpawnParameters SpawnParams;
|
|
SpawnParams.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AlwaysSpawn;
|
|
AExponentialHeightFog* Fog = World->SpawnActor<AExponentialHeightFog>(FVector::ZeroVector, FRotator::ZeroRotator, SpawnParams);
|
|
if (Fog)
|
|
{
|
|
#if WITH_EDITOR
|
|
Fog->SetActorLabel(TEXT("Ralpha_Fog"));
|
|
#endif
|
|
CreatedActors.Add(TEXT("ExponentialHeightFog"));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
ExistingActors.Add(TEXT("ExponentialHeightFog"));
|
|
}
|
|
|
|
// Check/Create Sky Atmosphere (physically-based sky)
|
|
bool bHasSkyAtmosphere = false;
|
|
for (TActorIterator<ASkyAtmosphere> It(World); It; ++It)
|
|
{
|
|
bHasSkyAtmosphere = true;
|
|
break;
|
|
}
|
|
if (!bHasSkyAtmosphere)
|
|
{
|
|
FActorSpawnParameters SpawnParams;
|
|
SpawnParams.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AlwaysSpawn;
|
|
ASkyAtmosphere* SkyAtmo = World->SpawnActor<ASkyAtmosphere>(FVector::ZeroVector, FRotator::ZeroRotator, SpawnParams);
|
|
if (SkyAtmo)
|
|
{
|
|
#if WITH_EDITOR
|
|
SkyAtmo->SetActorLabel(TEXT("Ralpha_SkyAtmosphere"));
|
|
#endif
|
|
CreatedActors.Add(TEXT("SkyAtmosphere"));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
ExistingActors.Add(TEXT("SkyAtmosphere"));
|
|
}
|
|
|
|
// Check/Create Volumetric Cloud
|
|
bool bHasVolumetricCloud = false;
|
|
for (TActorIterator<AVolumetricCloud> It(World); It; ++It)
|
|
{
|
|
bHasVolumetricCloud = true;
|
|
break;
|
|
}
|
|
if (!bHasVolumetricCloud)
|
|
{
|
|
FActorSpawnParameters SpawnParams;
|
|
SpawnParams.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AlwaysSpawn;
|
|
AVolumetricCloud* Cloud = World->SpawnActor<AVolumetricCloud>(FVector::ZeroVector, FRotator::ZeroRotator, SpawnParams);
|
|
if (Cloud)
|
|
{
|
|
#if WITH_EDITOR
|
|
Cloud->SetActorLabel(TEXT("Ralpha_VolumetricCloud"));
|
|
#endif
|
|
CreatedActors.Add(TEXT("VolumetricCloud"));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
ExistingActors.Add(TEXT("VolumetricCloud"));
|
|
}
|
|
|
|
// Check/Create CineCameraActor
|
|
if (!FindCineCamera())
|
|
{
|
|
FActorSpawnParameters SpawnParams;
|
|
SpawnParams.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AlwaysSpawn;
|
|
ACineCameraActor* Camera = World->SpawnActor<ACineCameraActor>(FVector(0.0f, -500.0f, 200.0f), FRotator(0.0f, 90.0f, 0.0f), SpawnParams);
|
|
if (Camera)
|
|
{
|
|
#if WITH_EDITOR
|
|
Camera->SetActorLabel(TEXT("Ralpha_Camera"));
|
|
#endif
|
|
CreatedActors.Add(TEXT("CineCameraActor"));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
ExistingActors.Add(TEXT("CineCameraActor"));
|
|
}
|
|
|
|
Result->SetBoolField(TEXT("success"), true);
|
|
|
|
TArray<TSharedPtr<FJsonValue>> CreatedArray;
|
|
for (const FString& Actor : CreatedActors)
|
|
{
|
|
CreatedArray.Add(MakeShared<FJsonValueString>(Actor));
|
|
}
|
|
Result->SetArrayField(TEXT("created"), CreatedArray);
|
|
|
|
TArray<TSharedPtr<FJsonValue>> ExistingArray;
|
|
for (const FString& Actor : ExistingActors)
|
|
{
|
|
ExistingArray.Add(MakeShared<FJsonValueString>(Actor));
|
|
}
|
|
Result->SetArrayField(TEXT("existing"), ExistingArray);
|
|
|
|
return Result;
|
|
}
|
|
|
|
// ============================================================================
|
|
// Point Light
|
|
// ============================================================================
|
|
|
|
bool URalphaParameterBridge::SpawnPointLight(const FVector& Location, float Intensity, const FLinearColor& Color, float AttenuationRadius, const FString& ActorLabel, FString& OutActorId)
|
|
{
|
|
UWorld* World = GetCurrentWorld();
|
|
if (!World) return false;
|
|
|
|
FActorSpawnParameters SpawnParams;
|
|
SpawnParams.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AlwaysSpawn;
|
|
|
|
APointLight* Light = World->SpawnActor<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;
|
|
}
|