Add material assignment commands for runtime material control
- Add set_actor_material command to apply existing materials to actors - Add set_actor_simple_material command to create dynamic materials with customizable base color, metallic, and roughness properties - Enables water-like reflective surfaces without pre-made assets Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
0a9ce7b371
commit
a568b60783
|
|
@ -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<FJsonObject>* 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<FJsonObject> 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);
|
||||
|
|
|
|||
|
|
@ -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 TSharedPtr<FJso
|
|||
}
|
||||
|
||||
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>();
|
||||
|
|
@ -481,6 +533,28 @@ bool URalphaParameterBridge::SetCameraParameters(const TSharedPtr<FJsonObject>&
|
|||
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")));
|
||||
|
|
@ -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<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;
|
||||
}
|
||||
|
||||
TArray<TSharedPtr<FJsonValue>> URalphaParameterBridge::ListActors(const FString& ClassFilter, const FString& NameFilter, bool bIncludeTransforms)
|
||||
{
|
||||
TArray<TSharedPtr<FJsonValue>> Results;
|
||||
|
|
@ -977,6 +1175,56 @@ TSharedPtr<FJsonObject> URalphaParameterBridge::SetupScene()
|
|||
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())
|
||||
{
|
||||
|
|
|
|||
|
|
@ -62,6 +62,10 @@ public:
|
|||
TArray<TSharedPtr<FJsonValue>> 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<FJsonObject> 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<FJsonObject> 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();
|
||||
};
|
||||
|
|
|
|||
Loading…
Reference in New Issue