diff --git a/Plugins/RalphaPlugin/Source/RalphaCore/Private/RalphaMCPServer.cpp b/Plugins/RalphaPlugin/Source/RalphaCore/Private/RalphaMCPServer.cpp index 6673c126..847d19d1 100644 --- a/Plugins/RalphaPlugin/Source/RalphaCore/Private/RalphaMCPServer.cpp +++ b/Plugins/RalphaPlugin/Source/RalphaCore/Private/RalphaMCPServer.cpp @@ -200,10 +200,23 @@ FString URalphaMCPServer::ProcessCommand(const FString& JsonCommand) { int32 Width = JsonObject->HasField(TEXT("width")) ? JsonObject->GetIntegerField(TEXT("width")) : 1920; int32 Height = JsonObject->HasField(TEXT("height")) ? JsonObject->GetIntegerField(TEXT("height")) : 1080; + bool bUseCamera = JsonObject->HasField(TEXT("use_camera")) ? JsonObject->GetBoolField(TEXT("use_camera")) : true; FString Base64Image; FString FilePath; - bool bSuccess = ScreenCapture->CaptureScreenshot(Width, Height, Base64Image, FilePath); + bool bSuccess = false; + + if (bUseCamera) + { + // Capture from CineCamera using SceneCapture2D + bSuccess = ScreenCapture->CaptureFromCamera(Width, Height, Base64Image, FilePath); + } + + // Fallback to viewport capture if camera capture failed or not requested + if (!bSuccess) + { + bSuccess = ScreenCapture->CaptureScreenshot(Width, Height, Base64Image, FilePath); + } ResponseObject->SetBoolField(TEXT("success"), bSuccess); if (bSuccess) @@ -337,6 +350,39 @@ FString URalphaMCPServer::ProcessCommand(const FString& JsonCommand) bool bSuccess = ParameterBridge->DeleteActor(ActorId); ResponseObject->SetBoolField(TEXT("success"), bSuccess); } + else if (CommandType == TEXT("set_actor_material")) + { + FString ActorId = JsonObject->GetStringField(TEXT("actor_id")); + FString MaterialPath = JsonObject->GetStringField(TEXT("material_path")); + int32 MaterialIndex = JsonObject->HasField(TEXT("material_index")) ? JsonObject->GetIntegerField(TEXT("material_index")) : 0; + bool bSuccess = ParameterBridge->SetActorMaterial(ActorId, MaterialPath, MaterialIndex); + ResponseObject->SetBoolField(TEXT("success"), bSuccess); + } + else if (CommandType == TEXT("set_actor_simple_material")) + { + FString ActorId = JsonObject->GetStringField(TEXT("actor_id")); + + // Parse color - supports hex string or RGB object + FLinearColor BaseColor = FLinearColor(0.1f, 0.2f, 0.4f); // Default dark blue + if (JsonObject->HasField(TEXT("color"))) + { + const TSharedPtr* ColorObj; + if (JsonObject->TryGetObjectField(TEXT("color"), ColorObj)) + { + BaseColor.R = (*ColorObj)->HasField(TEXT("r")) ? (*ColorObj)->GetNumberField(TEXT("r")) : 0.1f; + BaseColor.G = (*ColorObj)->HasField(TEXT("g")) ? (*ColorObj)->GetNumberField(TEXT("g")) : 0.2f; + BaseColor.B = (*ColorObj)->HasField(TEXT("b")) ? (*ColorObj)->GetNumberField(TEXT("b")) : 0.4f; + BaseColor.A = (*ColorObj)->HasField(TEXT("a")) ? (*ColorObj)->GetNumberField(TEXT("a")) : 1.0f; + } + } + + float Metallic = JsonObject->HasField(TEXT("metallic")) ? JsonObject->GetNumberField(TEXT("metallic")) : 0.0f; + float Roughness = JsonObject->HasField(TEXT("roughness")) ? JsonObject->GetNumberField(TEXT("roughness")) : 0.5f; + float Opacity = JsonObject->HasField(TEXT("opacity")) ? JsonObject->GetNumberField(TEXT("opacity")) : 1.0f; + + bool bSuccess = ParameterBridge->SetActorSimpleMaterial(ActorId, BaseColor, Metallic, Roughness, Opacity); + ResponseObject->SetBoolField(TEXT("success"), bSuccess); + } else if (CommandType == TEXT("list_actors")) { FString ClassFilter = JsonObject->HasField(TEXT("class_filter")) ? JsonObject->GetStringField(TEXT("class_filter")) : TEXT(""); @@ -366,6 +412,51 @@ FString URalphaMCPServer::ProcessCommand(const FString& JsonCommand) TSharedPtr SetupResult = ParameterBridge->SetupScene(); ResponseObject = SetupResult; } + else if (CommandType == TEXT("exec_command")) + { + FString Command = JsonObject->GetStringField(TEXT("command")); + if (!Command.IsEmpty()) + { + UWorld* World = GEngine->GetWorldContexts()[0].World(); + if (World) + { + GEngine->Exec(World, *Command); + ResponseObject->SetBoolField(TEXT("success"), true); + ResponseObject->SetStringField(TEXT("executed"), Command); + } + else + { + ResponseObject->SetBoolField(TEXT("success"), false); + ResponseObject->SetStringField(TEXT("error"), TEXT("No world available")); + } + } + else + { + ResponseObject->SetBoolField(TEXT("success"), false); + ResponseObject->SetStringField(TEXT("error"), TEXT("Missing command")); + } + } + else if (CommandType == TEXT("new_level")) + { + FString Template = JsonObject->HasField(TEXT("template")) ? JsonObject->GetStringField(TEXT("template")) : TEXT("Basic"); +#if WITH_EDITOR + if (GEditor) + { + // Create new level from template + GEditor->Exec(nullptr, TEXT("MAP NEW")); + ResponseObject->SetBoolField(TEXT("success"), true); + ResponseObject->SetStringField(TEXT("message"), TEXT("New level created")); + } + else + { + ResponseObject->SetBoolField(TEXT("success"), false); + ResponseObject->SetStringField(TEXT("error"), TEXT("Editor not available")); + } +#else + ResponseObject->SetBoolField(TEXT("success"), false); + ResponseObject->SetStringField(TEXT("error"), TEXT("Only available in editor")); +#endif + } else { ResponseObject->SetBoolField(TEXT("success"), false); diff --git a/Plugins/RalphaPlugin/Source/RalphaCore/Private/RalphaParameterBridge.cpp b/Plugins/RalphaPlugin/Source/RalphaCore/Private/RalphaParameterBridge.cpp index feba6c77..29df2048 100644 --- a/Plugins/RalphaPlugin/Source/RalphaCore/Private/RalphaParameterBridge.cpp +++ b/Plugins/RalphaPlugin/Source/RalphaCore/Private/RalphaParameterBridge.cpp @@ -19,9 +19,12 @@ #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() { @@ -312,9 +315,58 @@ bool URalphaParameterBridge::SetDirectionalLightParameters(const TSharedPtrMarkRenderStateDirty(); + + // 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(); @@ -481,6 +533,28 @@ bool URalphaParameterBridge::SetCameraParameters(const TSharedPtr& 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"))); @@ -695,6 +769,130 @@ bool URalphaParameterBridge::DeleteActor(const FString& ActorId) 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; @@ -977,6 +1175,56 @@ TSharedPtr URalphaParameterBridge::SetupScene() 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()) { diff --git a/Plugins/RalphaPlugin/Source/RalphaCore/Public/RalphaParameterBridge.h b/Plugins/RalphaPlugin/Source/RalphaCore/Public/RalphaParameterBridge.h index 182b8ec6..4d695b23 100644 --- a/Plugins/RalphaPlugin/Source/RalphaCore/Public/RalphaParameterBridge.h +++ b/Plugins/RalphaPlugin/Source/RalphaCore/Public/RalphaParameterBridge.h @@ -62,6 +62,10 @@ public: TArray> ListActors(const FString& ClassFilter, const FString& NameFilter, bool bIncludeTransforms); bool SetActorTransform(const FString& ActorId, const FVector* Location, const FRotator* Rotation, const float* Scale, bool bRelative); TSharedPtr GetActorDetails(const FString& ActorId, bool bIncludeComponents, bool bIncludeMaterials); + bool SetActorMaterial(const FString& ActorId, const FString& MaterialPath, int32 MaterialIndex); + + // Create a simple material with specified properties and apply to actor + bool SetActorSimpleMaterial(const FString& ActorId, const FLinearColor& BaseColor, float Metallic, float Roughness, float Opacity); // Scene Setup - creates required actors for rendering control TSharedPtr SetupScene(); @@ -80,4 +84,7 @@ private: // Parse hex color string to FLinearColor FLinearColor ParseHexColor(const FString& HexColor); FString ColorToHex(const FLinearColor& Color); + + // Refresh BP_Sky_Sphere when sun position changes + void RefreshSkySphere(); };