#include "DismembermentGraph/DismembermentPreviewManager.h" #include "DismembermentGraph/DismembermentGraphNode.h" #include "DismembermentGraph/DismembermentGraphNodeCut.h" #include "DismembermentGraph/DismembermentGraphNodeBoneSelect.h" #include "DismembermentGraph/DismembermentGraphNodeBloodEffect.h" #include "DismembermentGraph/DismembermentGraphNodePhysics.h" #include "DismembermentGraph/DismembermentGraphNodeOrgan.h" #include "DismembermentGraph/DismembermentGraphNodeWound.h" #include "Components/SkeletalMeshComponent.h" #include "Components/StaticMeshComponent.h" #include "Components/DecalComponent.h" #include "NiagaraComponent.h" #include "NiagaraSystem.h" #include "Engine/StaticMesh.h" #include "Materials/MaterialInterface.h" #include "GameFramework/Actor.h" #include "Engine/World.h" // Define log category DEFINE_LOG_CATEGORY(LogFLESHPreview); UDismembermentPreviewManager::UDismembermentPreviewManager() : World(nullptr) , TargetActor(nullptr) , TargetSkeletalMesh(nullptr) , PreviewedNode(nullptr) , PreviewCutPlaneMesh(nullptr) { } void UDismembermentPreviewManager::Initialize(TObjectPtr InWorld) { World = InWorld; UE_LOG(LogFLESHPreview, Log, TEXT("Preview manager initialized")); } void UDismembermentPreviewManager::Cleanup() { UE_LOG(LogFLESHPreview, Log, TEXT("Cleaning up preview manager")); ClearPreview(); World = nullptr; TargetActor = nullptr; TargetSkeletalMesh = nullptr; } void UDismembermentPreviewManager::SetTargetActor(TObjectPtr InActor) { // Clear any existing preview ClearPreview(); // Set the new target actor TargetActor = InActor; if (TargetActor) { UE_LOG(LogFLESHPreview, Log, TEXT("Set target actor: %s"), *TargetActor->GetName()); // Find the skeletal mesh component FindTargetSkeletalMesh(); } else { UE_LOG(LogFLESHPreview, Warning, TEXT("Set empty target actor")); } } bool UDismembermentPreviewManager::PreviewNode(TObjectPtr Node) { // Clear any existing preview ClearPreview(); // Set the new previewed node PreviewedNode = Node; // Check if we have a valid target if (!TargetActor || !TargetSkeletalMesh) { UE_LOG(LogFLESHPreview, Warning, TEXT("Cannot preview node: Invalid target actor or skeletal mesh")); return false; } if (!Node) { UE_LOG(LogFLESHPreview, Warning, TEXT("Cannot preview node: Node is null")); return false; } UE_LOG(LogFLESHPreview, Log, TEXT("Previewing node: %s"), *Node->GetNodeTitle(ENodeTitleType::FullTitle).ToString()); // Preview based on node type if (TObjectPtr CutNode = Cast(Node)) { return PreviewCutNode(CutNode); } else if (TObjectPtr BoneSelectNode = Cast(Node)) { return PreviewBoneSelectNode(BoneSelectNode); } else if (TObjectPtr BloodEffectNode = Cast(Node)) { return PreviewBloodEffectNode(BloodEffectNode); } else if (TObjectPtr PhysicsNode = Cast(Node)) { return PreviewPhysicsNode(PhysicsNode); } else if (TObjectPtr OrganNode = Cast(Node)) { return PreviewOrganNode(OrganNode); } else if (TObjectPtr WoundNode = Cast(Node)) { return PreviewWoundNode(WoundNode); } UE_LOG(LogFLESHPreview, Warning, TEXT("Unknown node type")); return false; } void UDismembermentPreviewManager::ClearPreview() { // Clear the previewed node PreviewedNode = nullptr; // Clear all preview components ClearPreviewComponents(); // Clear preview data PreviewBoneSelections.Empty(); PreviewCutTransforms.Empty(); PreviewBloodEffectTransforms.Empty(); PreviewOrganTransforms.Empty(); PreviewWoundTransforms.Empty(); UE_LOG(LogFLESHPreview, Verbose, TEXT("Preview cleared")); } void UDismembermentPreviewManager::Tick(float DeltaTime) { // Update preview effects if (PreviewedNode && TargetActor && TargetSkeletalMesh) { // Update based on node type if (UDismembermentGraphNodeCut* CutNode = Cast(PreviewedNode)) { // Update cut preview } else if (UDismembermentGraphNodeBloodEffect* BloodEffectNode = Cast(PreviewedNode)) { // Update blood effect preview } else if (UDismembermentGraphNodePhysics* PhysicsNode = Cast(PreviewedNode)) { // Update physics preview } } } bool UDismembermentPreviewManager::FindTargetSkeletalMesh() { TargetSkeletalMesh = nullptr; if (TargetActor) { // Find the first skeletal mesh component TargetSkeletalMesh = TargetActor->FindComponentByClass(); if (TargetSkeletalMesh) { UE_LOG(LogFLESHPreview, Log, TEXT("Found target skeletal mesh: %s"), *TargetSkeletalMesh->GetName()); } else { UE_LOG(LogFLESHPreview, Warning, TEXT("No skeletal mesh component found on actor %s"), *TargetActor->GetName()); } } else { UE_LOG(LogFLESHPreview, Warning, TEXT("Cannot find skeletal mesh: Target actor is null")); } return TargetSkeletalMesh != nullptr; } bool UDismembermentPreviewManager::PreviewCutNode(TObjectPtr CutNode) { if (!CutNode || !TargetActor || !TargetSkeletalMesh) { UE_LOG(LogFLESHPreview, Warning, TEXT("Cannot preview cut node: Invalid parameters")); return false; } // Get the cut parameters float CutWidth = CutNode->CutWidth; float CutDepth = CutNode->CutDepth; UMaterialInterface* CutMaterial = CutNode->bUseCustomMaterial ? CutNode->CustomCutMaterial : nullptr; // Get the actor's transform FTransform ActorTransform = TargetActor->GetActorTransform(); // Create a default cut direction if not connected to an input FVector CutLocation = ActorTransform.GetLocation() + FVector(0.0f, 0.0f, 100.0f); FVector CutDirection = FVector(1.0f, 0.0f, 0.0f); // Create the preview cut plane mesh PreviewCutPlaneMesh = CreatePreviewCutPlaneMesh(CutLocation, CutDirection, CutWidth, CutDepth, CutMaterial); if (!PreviewCutPlaneMesh) { UE_LOG(LogFLESHPreview, Error, TEXT("Failed to create cut plane mesh")); return false; } // Store the cut transform for later use FTransform CutTransform; CutTransform.SetLocation(CutLocation); CutTransform.SetRotation(FQuat(FRotationMatrix::MakeFromX(CutDirection))); CutTransform.SetScale3D(FVector(CutWidth, CutDepth, 1.0f)); PreviewCutTransforms.Add(CutTransform); UE_LOG(LogFLESHPreview, Log, TEXT("Cut node preview created successfully")); return PreviewCutPlaneMesh != nullptr; } bool UDismembermentPreviewManager::PreviewBoneSelectNode(TObjectPtr BoneSelectNode) { if (!BoneSelectNode || !TargetActor || !TargetSkeletalMesh) { UE_LOG(LogFLESHPreview, Warning, TEXT("Cannot preview bone select node: Invalid parameters")); return false; } // Get the bone selection parameters TArray BoneNames = BoneSelectNode->BoneNames; bool bUseRegex = BoneSelectNode->bUseRegex; FString BoneNamePattern = BoneSelectNode->BoneNamePattern; bool bIncludeChildren = BoneSelectNode->bIncludeChildren; // Store the bone selections for later use PreviewBoneSelections.Append(BoneNames); UE_LOG(LogFLESHPreview, Log, TEXT("Bone select node preview created successfully, selected %d bones"), BoneNames.Num()); // TODO: Handle regex and child bones return true; } bool UDismembermentPreviewManager::PreviewBloodEffectNode(TObjectPtr BloodEffectNode) { if (!BloodEffectNode || !TargetActor || !TargetSkeletalMesh) { UE_LOG(LogFLESHPreview, Warning, TEXT("Cannot preview blood effect node: Invalid parameters")); return false; } // Get the blood effect parameters UNiagaraSystem* BloodEffect = BloodEffectNode->BloodEffect; float BloodAmount = BloodEffectNode->BloodAmount; float BloodPressure = BloodEffectNode->BloodPressure; bool bCreateBloodPool = BloodEffectNode->bCreateBloodPool; float BloodPoolSize = BloodEffectNode->BloodPoolSize; UMaterialInterface* BloodPoolMaterial = BloodEffectNode->BloodPoolMaterial; if (!BloodEffect) { UE_LOG(LogFLESHPreview, Warning, TEXT("Cannot preview blood effect: No blood effect system specified")); return false; } // Get the actor's transform FTransform ActorTransform = TargetActor->GetActorTransform(); // Create a default blood effect location if not connected to an input FVector BloodLocation = ActorTransform.GetLocation() + FVector(0.0f, 0.0f, 100.0f); // Create the preview blood effect TObjectPtr BloodEffectComponent = CreatePreviewBloodEffect(BloodLocation, BloodEffect, BloodAmount, BloodPressure); if (!BloodEffectComponent) { UE_LOG(LogFLESHPreview, Error, TEXT("Failed to create blood effect component")); return false; } // Create the preview blood pool if needed TObjectPtr BloodPoolComponent = nullptr; if (bCreateBloodPool) { BloodPoolComponent = CreatePreviewBloodPool(BloodLocation, BloodPoolSize, BloodPoolMaterial); if (!BloodPoolComponent) { UE_LOG(LogFLESHPreview, Warning, TEXT("Failed to create blood pool component")); } } // Store the blood effect transform for later use FTransform BloodEffectTransform; BloodEffectTransform.SetLocation(BloodLocation); PreviewBloodEffectTransforms.Add(BloodEffectTransform); UE_LOG(LogFLESHPreview, Log, TEXT("Blood effect node preview created successfully")); return BloodEffectComponent != nullptr; } bool UDismembermentPreviewManager::PreviewPhysicsNode(TObjectPtr PhysicsNode) { if (!PhysicsNode || !TargetActor || !TargetSkeletalMesh) { UE_LOG(LogFLESHPreview, Warning, TEXT("Cannot preview physics node: Invalid parameters")); return false; } // Get the physics parameters float Mass = PhysicsNode->Mass; float LinearDamping = PhysicsNode->LinearDamping; float AngularDamping = PhysicsNode->AngularDamping; bool bEnableGravity = PhysicsNode->bEnableGravity; bool bSimulatePhysics = PhysicsNode->bSimulatePhysics; bool bGenerateOverlapEvents = PhysicsNode->bGenerateOverlapEvents; UPhysicalMaterial* PhysicalMaterial = PhysicsNode->PhysicalMaterial; float ImpulseForce = PhysicsNode->ImpulseForce; float ImpulseRadius = PhysicsNode->ImpulseRadius; // Apply physics settings to the target skeletal mesh if (TargetSkeletalMesh) { // Store original values to restore later TargetSkeletalMesh->SetMassOverrideInKg(NAME_None, Mass, true); TargetSkeletalMesh->SetLinearDamping(LinearDamping); TargetSkeletalMesh->SetAngularDamping(AngularDamping); TargetSkeletalMesh->SetEnableGravity(bEnableGravity); TargetSkeletalMesh->SetSimulatePhysics(bSimulatePhysics); TargetSkeletalMesh->SetGenerateOverlapEvents(bGenerateOverlapEvents); if (PhysicalMaterial) { TargetSkeletalMesh->SetPhysMaterialOverride(PhysicalMaterial); } UE_LOG(LogFLESHPreview, Log, TEXT("Physics node preview applied successfully, mass: %.2f, linear damping: %.2f, angular damping: %.2f"), Mass, LinearDamping, AngularDamping); } return true; } bool UDismembermentPreviewManager::PreviewOrganNode(TObjectPtr OrganNode) { if (!OrganNode || !TargetActor || !TargetSkeletalMesh) { UE_LOG(LogFLESHPreview, Warning, TEXT("Cannot preview organ node: Invalid parameters")); return false; } // Get the organ parameters UStaticMesh* OrganMesh = OrganNode->OrganMesh; UMaterialInterface* OrganMaterial = OrganNode->OrganMaterial; FName AttachBoneName = OrganNode->AttachBoneName; FVector RelativeLocation = OrganNode->RelativeLocation; FRotator RelativeRotation = OrganNode->RelativeRotation; FVector RelativeScale = OrganNode->RelativeScale; bool bSimulatePhysics = OrganNode->bSimulatePhysics; float DamageMultiplier = OrganNode->DamageMultiplier; bool bIsCriticalOrgan = OrganNode->bIsCriticalOrgan; float BloodAmount = OrganNode->BloodAmount; if (!OrganMesh) { UE_LOG(LogFLESHPreview, Warning, TEXT("Cannot preview organ: No organ mesh specified")); return false; } // Create the preview organ TObjectPtr OrganComponent = CreatePreviewOrgan(OrganMesh, OrganMaterial, AttachBoneName, RelativeLocation, RelativeRotation, RelativeScale); if (!OrganComponent) { UE_LOG(LogFLESHPreview, Error, TEXT("Failed to create organ component")); return false; } // Store the organ transform for later use FTransform OrganTransform; if (TargetSkeletalMesh && !AttachBoneName.IsNone()) { FTransform BoneTransform = TargetSkeletalMesh->GetBoneTransform(TargetSkeletalMesh->GetBoneIndex(AttachBoneName)); OrganTransform = FTransform(RelativeRotation, RelativeLocation, RelativeScale) * BoneTransform; } else { OrganTransform = FTransform(RelativeRotation, RelativeLocation, RelativeScale) * TargetActor->GetActorTransform(); } PreviewOrganTransforms.Add(OrganTransform); UE_LOG(LogFLESHPreview, Log, TEXT("Organ node preview created successfully, attached to bone: %s"), AttachBoneName.IsNone() ? TEXT("None") : *AttachBoneName.ToString()); return OrganComponent != nullptr; } bool UDismembermentPreviewManager::PreviewWoundNode(TObjectPtr WoundNode) { if (!WoundNode || !TargetActor || !TargetSkeletalMesh) { UE_LOG(LogFLESHPreview, Warning, TEXT("Cannot preview wound node: Invalid parameters")); return false; } // Get the wound parameters float WoundSize = WoundNode->WoundSize; float WoundDepth = WoundNode->WoundDepth; UMaterialInterface* WoundMaterial = WoundNode->WoundMaterial; UNiagaraSystem* WoundEffect = WoundNode->WoundEffect; bool bCreateDecal = WoundNode->bCreateDecal; UMaterialInterface* DecalMaterial = WoundNode->DecalMaterial; float DecalSize = WoundNode->DecalSize; float DecalLifetime = WoundNode->DecalLifetime; bool bAffectBoneHealth = WoundNode->bAffectBoneHealth; float BoneDamage = WoundNode->BoneDamage; // Get the actor's transform FTransform ActorTransform = TargetActor->GetActorTransform(); // Create a default wound location if not connected to an input FVector WoundLocation = ActorTransform.GetLocation() + FVector(0.0f, 0.0f, 100.0f); // Create the preview wound effect TObjectPtr WoundEffectComponent = nullptr; if (WoundEffect) { WoundEffectComponent = UNiagaraFunctionLibrary::SpawnSystemAttached( WoundEffect, TargetSkeletalMesh, NAME_None, WoundLocation, FRotator::ZeroRotator, EAttachLocation::KeepWorldPosition, false); if (WoundEffectComponent) { PreviewNiagaraComponents.Add(WoundEffectComponent); } else { UE_LOG(LogFLESHPreview, Warning, TEXT("Failed to create wound effect component")); } } // Create the preview wound decal if needed TObjectPtr WoundDecalComponent = nullptr; if (bCreateDecal && DecalMaterial) { WoundDecalComponent = CreatePreviewWound(WoundLocation, DecalSize, DecalMaterial); if (!WoundDecalComponent) { UE_LOG(LogFLESHPreview, Warning, TEXT("Failed to create wound decal component")); } } // Store the wound transform for later use FTransform WoundTransform; WoundTransform.SetLocation(WoundLocation); PreviewWoundTransforms.Add(WoundTransform); UE_LOG(LogFLESHPreview, Log, TEXT("Wound node preview created successfully, size: %.2f, depth: %.2f"), WoundSize, WoundDepth); return WoundEffectComponent != nullptr || WoundDecalComponent != nullptr; } TObjectPtr UDismembermentPreviewManager::CreatePreviewCutPlaneMesh(const FVector& Location, const FVector& Direction, float Width, float Depth, UMaterialInterface* Material) { if (!World || !TargetActor) { UE_LOG(LogFLESHPreview, Warning, TEXT("Cannot create cut plane: Invalid world or target actor")); return nullptr; } // Create a static mesh component for the cut plane TObjectPtr CutPlaneMeshComponent = NewObject(TargetActor); if (!CutPlaneMeshComponent) { UE_LOG(LogFLESHPreview, Error, TEXT("Failed to create cut plane component")); return nullptr; } // Register the component CutPlaneMeshComponent->RegisterComponent(); // Set the mesh to a plane UStaticMesh* PlaneMesh = LoadObject(nullptr, TEXT("/Engine/BasicShapes/Plane.Plane")); if (PlaneMesh) { CutPlaneMeshComponent->SetStaticMesh(PlaneMesh); } else { UE_LOG(LogFLESHPreview, Warning, TEXT("Failed to load plane mesh")); } // Set the material if (Material) { CutPlaneMeshComponent->SetMaterial(0, Material); } else { // Use a default material UMaterialInterface* DefaultMaterial = LoadObject(nullptr, TEXT("/Engine/BasicShapes/BasicShapeMaterial.BasicShapeMaterial")); if (DefaultMaterial) { CutPlaneMeshComponent->SetMaterial(0, DefaultMaterial); } else { UE_LOG(LogFLESHPreview, Warning, TEXT("Failed to load default material")); } } // Set the transform FRotator Rotation = FRotationMatrix::MakeFromX(Direction).Rotator(); CutPlaneMeshComponent->SetWorldLocationAndRotation(Location, Rotation); CutPlaneMeshComponent->SetWorldScale3D(FVector(Width, Depth, 1.0f)); // Add to the preview components PreviewStaticMeshComponents.Add(CutPlaneMeshComponent); UE_LOG(LogFLESHPreview, Verbose, TEXT("Created cut plane mesh, location: %s, direction: %s"), *Location.ToString(), *Direction.ToString()); return CutPlaneMeshComponent; } TObjectPtr UDismembermentPreviewManager::CreatePreviewBloodEffect(const FVector& Location, UNiagaraSystem* BloodEffect, float BloodAmount, float BloodPressure) { if (!World || !TargetActor || !BloodEffect) { UE_LOG(LogFLESHPreview, Warning, TEXT("Cannot create blood effect: Invalid parameters")); return nullptr; } // Create a Niagara component for the blood effect TObjectPtr BloodEffectComponent = UNiagaraFunctionLibrary::SpawnSystemAttached( BloodEffect, TargetSkeletalMesh, NAME_None, Location, FRotator::ZeroRotator, EAttachLocation::KeepWorldPosition, false); if (!BloodEffectComponent) { UE_LOG(LogFLESHPreview, Error, TEXT("Failed to create blood effect component")); return nullptr; } // Set parameters BloodEffectComponent->SetFloatParameter(TEXT("BloodAmount"), BloodAmount); BloodEffectComponent->SetFloatParameter(TEXT("BloodPressure"), BloodPressure); // Add to the preview components PreviewNiagaraComponents.Add(BloodEffectComponent); UE_LOG(LogFLESHPreview, Verbose, TEXT("Created blood effect, location: %s, blood amount: %.2f, blood pressure: %.2f"), *Location.ToString(), BloodAmount, BloodPressure); return BloodEffectComponent; } TObjectPtr UDismembermentPreviewManager::CreatePreviewBloodPool(const FVector& Location, float Size, UMaterialInterface* Material) { if (!World || !TargetActor) { UE_LOG(LogFLESHPreview, Warning, TEXT("Cannot create blood pool: Invalid world or target actor")); return nullptr; } // Create a decal component for the blood pool TObjectPtr BloodPoolComponent = NewObject(TargetActor); if (!BloodPoolComponent) { UE_LOG(LogFLESHPreview, Error, TEXT("Failed to create blood pool component")); return nullptr; } // Register the component BloodPoolComponent->RegisterComponent(); // Set the material if (Material) { BloodPoolComponent->SetMaterial(0, Material); } else { // Use a default material UMaterialInterface* DefaultMaterial = LoadObject(nullptr, TEXT("/Engine/BasicShapes/BasicShapeMaterial.BasicShapeMaterial")); if (DefaultMaterial) { BloodPoolComponent->SetMaterial(0, DefaultMaterial); } else { UE_LOG(LogFLESHPreview, Warning, TEXT("Failed to load default material")); } } // Set the transform BloodPoolComponent->SetWorldLocation(Location); BloodPoolComponent->SetWorldRotation(FRotator(-90.0f, 0.0f, 0.0f)); BloodPoolComponent->DecalSize = FVector(Size, Size, 10.0f); // Add to the preview components PreviewDecalComponents.Add(BloodPoolComponent); UE_LOG(LogFLESHPreview, Verbose, TEXT("Created blood pool decal, location: %s, size: %.2f"), *Location.ToString(), Size); return BloodPoolComponent; } TObjectPtr UDismembermentPreviewManager::CreatePreviewOrgan(UStaticMesh* OrganMesh, UMaterialInterface* OrganMaterial, const FName& AttachBoneName, const FVector& RelativeLocation, const FRotator& RelativeRotation, const FVector& RelativeScale) { if (!World || !TargetActor || !OrganMesh) { UE_LOG(LogFLESHPreview, Warning, TEXT("Cannot create organ: Invalid parameters")); return nullptr; } // Create a static mesh component for the organ TObjectPtr OrganComponent = NewObject(TargetActor); if (!OrganComponent) { UE_LOG(LogFLESHPreview, Error, TEXT("Failed to create organ component")); return nullptr; } // Register the component OrganComponent->RegisterComponent(); // Set the mesh OrganComponent->SetStaticMesh(OrganMesh); // Set the material if (OrganMaterial) { OrganComponent->SetMaterial(0, OrganMaterial); } // Attach to bone if specified if (TargetSkeletalMesh && !AttachBoneName.IsNone()) { if (TargetSkeletalMesh->GetBoneIndex(AttachBoneName) != INDEX_NONE) { OrganComponent->AttachToComponent(TargetSkeletalMesh, FAttachmentTransformRules::KeepRelativeTransform, AttachBoneName); OrganComponent->SetRelativeLocationAndRotation(RelativeLocation, RelativeRotation); OrganComponent->SetRelativeScale3D(RelativeScale); UE_LOG(LogFLESHPreview, Verbose, TEXT("Organ attached to bone: %s"), *AttachBoneName.ToString()); } else { UE_LOG(LogFLESHPreview, Warning, TEXT("Bone not found: %s, attaching to actor root component"), *AttachBoneName.ToString()); // Set the transform OrganComponent->SetWorldLocationAndRotation( TargetActor->GetActorLocation() + RelativeLocation, TargetActor->GetActorRotation() + RelativeRotation); OrganComponent->SetWorldScale3D(RelativeScale); } } else { // Set the transform OrganComponent->SetWorldLocationAndRotation( TargetActor->GetActorLocation() + RelativeLocation, TargetActor->GetActorRotation() + RelativeRotation); OrganComponent->SetWorldScale3D(RelativeScale); } // Add to the preview components PreviewStaticMeshComponents.Add(OrganComponent); UE_LOG(LogFLESHPreview, Verbose, TEXT("Created organ component, relative location: %s"), *RelativeLocation.ToString()); return OrganComponent; } TObjectPtr UDismembermentPreviewManager::CreatePreviewWound(const FVector& Location, float Size, UMaterialInterface* Material) { if (!World || !TargetActor) { UE_LOG(LogFLESHPreview, Warning, TEXT("Cannot create wound: Invalid world or target actor")); return nullptr; } // Create a decal component for the wound TObjectPtr WoundComponent = NewObject(TargetActor); if (!WoundComponent) { UE_LOG(LogFLESHPreview, Error, TEXT("Failed to create wound component")); return nullptr; } // Register the component WoundComponent->RegisterComponent(); // Set the material if (Material) { WoundComponent->SetMaterial(0, Material); } else { // Use a default material UMaterialInterface* DefaultMaterial = LoadObject(nullptr, TEXT("/Engine/BasicShapes/BasicShapeMaterial.BasicShapeMaterial")); if (DefaultMaterial) { WoundComponent->SetMaterial(0, DefaultMaterial); } else { UE_LOG(LogFLESHPreview, Warning, TEXT("Failed to load default material")); } } // Set the transform WoundComponent->SetWorldLocation(Location); WoundComponent->SetWorldRotation(FRotator(-90.0f, 0.0f, 0.0f)); WoundComponent->DecalSize = FVector(Size, Size, 10.0f); // Add to the preview components PreviewDecalComponents.Add(WoundComponent); UE_LOG(LogFLESHPreview, Verbose, TEXT("Created wound decal, location: %s, size: %.2f"), *Location.ToString(), Size); return WoundComponent; } void UDismembermentPreviewManager::ClearPreviewComponents() { // Destroy all preview components for (TObjectPtr Component : PreviewNiagaraComponents) { if (Component) { Component->DestroyComponent(); } } PreviewNiagaraComponents.Empty(); UE_LOG(LogFLESHPreview, Verbose, TEXT("Cleared %d Niagara components"), PreviewNiagaraComponents.Num()); for (TObjectPtr Component : PreviewDecalComponents) { if (Component) { Component->DestroyComponent(); } } PreviewDecalComponents.Empty(); UE_LOG(LogFLESHPreview, Verbose, TEXT("Cleared %d decal components"), PreviewDecalComponents.Num()); for (TObjectPtr Component : PreviewStaticMeshComponents) { if (Component) { Component->DestroyComponent(); } } PreviewStaticMeshComponents.Empty(); UE_LOG(LogFLESHPreview, Verbose, TEXT("Cleared %d static mesh components"), PreviewStaticMeshComponents.Num()); // Clear the cut plane mesh if (PreviewCutPlaneMesh) { PreviewCutPlaneMesh->DestroyComponent(); PreviewCutPlaneMesh = nullptr; } }