// 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" 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 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 It(World); It; ++It) { return *It; } return nullptr; } ADirectionalLight* URalphaParameterBridge::FindDirectionalLight() { UWorld* World = GetCurrentWorld(); if (!World) return nullptr; for (TActorIterator It(World); It; ++It) { return *It; } return nullptr; } ASkyLight* URalphaParameterBridge::FindSkyLight() { UWorld* World = GetCurrentWorld(); if (!World) return nullptr; for (TActorIterator It(World); It; ++It) { return *It; } return nullptr; } AExponentialHeightFog* URalphaParameterBridge::FindExponentialHeightFog() { UWorld* World = GetCurrentWorld(); if (!World) return nullptr; for (TActorIterator It(World); It; ++It) { return *It; } return nullptr; } ACineCameraActor* URalphaParameterBridge::FindCineCamera() { UWorld* World = GetCurrentWorld(); if (!World) return nullptr; for (TActorIterator 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 URalphaParameterBridge::GetAllParameters() { TSharedPtr Result = MakeShared(); 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& 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 URalphaParameterBridge::GetPostProcessParameters() { TSharedPtr Result = MakeShared(); 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& Params) { ADirectionalLight* Light = FindDirectionalLight(); if (!Light) { UE_LOG(LogRalphaMCP, Warning, TEXT("No DirectionalLight found in scene")); return false; } UDirectionalLightComponent* LightComp = Cast(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 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(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 URalphaParameterBridge::GetDirectionalLightParameters() { TSharedPtr Result = MakeShared(); ADirectionalLight* Light = FindDirectionalLight(); if (!Light) return Result; UDirectionalLightComponent* LightComp = Cast(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& 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 URalphaParameterBridge::GetSkyLightParameters() { TSharedPtr Result = MakeShared(); 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& 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 URalphaParameterBridge::GetFogParameters() { TSharedPtr Result = MakeShared(); 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& 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* 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* 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 URalphaParameterBridge::GetCameraParameters() { TSharedPtr Result = MakeShared(); 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& 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> URalphaParameterBridge::SearchAssets(const FString& Query, const FString& AssetType, int32 MaxResults) { TArray> Results; FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked("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 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 AssetObj = MakeShared(); AssetObj->SetStringField(TEXT("name"), AssetName); AssetObj->SetStringField(TEXT("asset_path"), Asset.GetObjectPathString()); AssetObj->SetStringField(TEXT("class"), Asset.AssetClassPath.GetAssetName().ToString()); Results.Add(MakeShared(AssetObj)); Count++; } } return Results; } TArray> URalphaParameterBridge::ListAssets(const FString& FolderPath, const FString& AssetType, bool bRecursive) { TArray> Results; FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked("AssetRegistry"); IAssetRegistry& AssetRegistry = AssetRegistryModule.Get(); FARFilter Filter; Filter.bRecursivePaths = bRecursive; Filter.PackagePaths.Add(*FolderPath); TArray AssetList; AssetRegistry.GetAssets(Filter, AssetList); for (const FAssetData& Asset : AssetList) { TSharedPtr AssetObj = MakeShared(); 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(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(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(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 It(World); It; ++It) { if (It->GetName() == ActorId) { World->DestroyActor(*It); return true; } } return false; } 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 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(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(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(); 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 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(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(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(); 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; } TArray> URalphaParameterBridge::ListActors(const FString& ClassFilter, const FString& NameFilter, bool bIncludeTransforms) { TArray> Results; UWorld* World = GetCurrentWorld(); if (!World) return Results; for (TActorIterator 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 ActorObj = MakeShared(); 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 TransformObj = MakeShared(); TSharedPtr LocObj = MakeShared(); LocObj->SetNumberField(TEXT("x"), Location.X); LocObj->SetNumberField(TEXT("y"), Location.Y); LocObj->SetNumberField(TEXT("z"), Location.Z); TransformObj->SetObjectField(TEXT("location"), LocObj); TSharedPtr RotObj = MakeShared(); RotObj->SetNumberField(TEXT("pitch"), Rotation.Pitch); RotObj->SetNumberField(TEXT("yaw"), Rotation.Yaw); RotObj->SetNumberField(TEXT("roll"), Rotation.Roll); TransformObj->SetObjectField(TEXT("rotation"), RotObj); TSharedPtr ScaleObj = MakeShared(); 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(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 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 URalphaParameterBridge::GetActorDetails(const FString& ActorId, bool bIncludeComponents, bool bIncludeMaterials) { TSharedPtr Result = MakeShared(); UWorld* World = GetCurrentWorld(); if (!World) return Result; for (TActorIterator 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 TransformObj = MakeShared(); TSharedPtr LocObj = MakeShared(); LocObj->SetNumberField(TEXT("x"), Location.X); LocObj->SetNumberField(TEXT("y"), Location.Y); LocObj->SetNumberField(TEXT("z"), Location.Z); TransformObj->SetObjectField(TEXT("location"), LocObj); TSharedPtr RotObj = MakeShared(); 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> ComponentsArray; TArray Components; Actor->GetComponents(Components); for (UActorComponent* Component : Components) { TSharedPtr CompObj = MakeShared(); CompObj->SetStringField(TEXT("name"), Component->GetName()); CompObj->SetStringField(TEXT("class"), Component->GetClass()->GetName()); ComponentsArray.Add(MakeShared(CompObj)); } Result->SetArrayField(TEXT("components"), ComponentsArray); } return Result; } } return Result; } // ============================================================================ // Scene Setup // ============================================================================ TSharedPtr URalphaParameterBridge::SetupScene() { TSharedPtr Result = MakeShared(); TArray CreatedActors; TArray 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(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 if (!FindDirectionalLight()) { FActorSpawnParameters SpawnParams; SpawnParams.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AlwaysSpawn; ADirectionalLight* Light = World->SpawnActor(FVector::ZeroVector, FRotator(-45.0f, 0.0f, 0.0f), SpawnParams); if (Light) { #if WITH_EDITOR Light->SetActorLabel(TEXT("Ralpha_Sun")); #endif CreatedActors.Add(TEXT("DirectionalLight")); } } else { ExistingActors.Add(TEXT("DirectionalLight")); } // Check/Create SkyLight if (!FindSkyLight()) { FActorSpawnParameters SpawnParams; SpawnParams.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AlwaysSpawn; ASkyLight* Light = World->SpawnActor(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(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 It(World); It; ++It) { bHasSkyAtmosphere = true; break; } if (!bHasSkyAtmosphere) { FActorSpawnParameters SpawnParams; SpawnParams.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AlwaysSpawn; ASkyAtmosphere* SkyAtmo = World->SpawnActor(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 It(World); It; ++It) { bHasVolumetricCloud = true; break; } if (!bHasVolumetricCloud) { FActorSpawnParameters SpawnParams; SpawnParams.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AlwaysSpawn; AVolumetricCloud* Cloud = World->SpawnActor(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(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> CreatedArray; for (const FString& Actor : CreatedActors) { CreatedArray.Add(MakeShared(Actor)); } Result->SetArrayField(TEXT("created"), CreatedArray); TArray> ExistingArray; for (const FString& Actor : ExistingActors) { ExistingArray.Add(MakeShared(Actor)); } Result->SetArrayField(TEXT("existing"), ExistingArray); return Result; }