更新 Source/FLESH/Private/BloodPool.cpp

This commit is contained in:
2025-04-18 18:14:05 +08:00
parent 37b715b346
commit 80fdce96b2

View File

@@ -1,166 +1,258 @@
#include "BloodSystem.h"
#include "NiagaraComponent.h"
#include "NiagaraFunctionLibrary.h"
#include "Components/DecalComponent.h"
#include "Kismet/GameplayStatics.h"
#include "Engine/World.h"
#include "BooleanCutTool.h"
#include "Engine/StaticMesh.h"
#include "Engine/SkeletalMesh.h"
#include "ProceduralMeshComponent.h"
#include "Materials/MaterialInterface.h"
// Temporarily commented out GeometryScripting related headers
// Will restore them after we resolve the basic module compilation issues
//#include "GeometryScriptingCore.h"
//#include "GeometryScript/MeshBooleanFunctions.h"
//#include "GeometryScript/MeshPrimitiveFunctions.h"
//#include "DynamicMesh/DynamicMesh3.h"
//#include "GeometryScript/MeshTransformFunctions.h"
// Sets default values for this component's properties
UBloodSystem::UBloodSystem()
// Constructor
UBooleanCutTool::UBooleanCutTool()
{
// Set this component to be initialized when the game starts, and to be ticked every frame
PrimaryComponentTick.bCanEverTick = true;
}
// Called when the game starts
void UBloodSystem::BeginPlay()
// Perform boolean cut on static mesh
TArray<UStaticMesh*> UBooleanCutTool::CutStaticMesh(UStaticMesh* TargetMesh, const FCutPlane& CutPlane, bool bCreateCap)
{
Super::BeginPlay();
TArray<UStaticMesh*> Result;
// Check if target mesh is valid
if (!TargetMesh)
{
return Result;
}
// Create cut plane mesh
UStaticMesh* CutPlaneMesh = CreateCutPlaneMesh(CutPlane);
if (!CutPlaneMesh)
{
return Result;
}
// Use GeometryScript to perform boolean cut
// Note: This is just a framework, actual implementation requires GeometryScript API
// Create positive part mesh
UStaticMesh* PositiveMesh = NewObject<UStaticMesh>();
// TODO: Use GeometryScript to perform boolean difference operation for positive part
// Create negative part mesh
UStaticMesh* NegativeMesh = NewObject<UStaticMesh>();
// TODO: Use GeometryScript to perform boolean difference operation for negative part
// If cap creation is needed
if (bCreateCap)
{
// TODO: Create cap for positive and negative parts
}
// Add to result array
Result.Add(PositiveMesh);
Result.Add(NegativeMesh);
return Result;
}
// Called every frame
void UBloodSystem::TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction)
// Perform boolean cut on skeletal mesh
TArray<USkeletalMesh*> UBooleanCutTool::CutSkeletalMesh(USkeletalMesh* TargetMesh, const FCutPlane& CutPlane, FName BoneName, bool bCreateCap)
{
Super::TickComponent(DeltaTime, TickType, ThisTickFunction);
TArray<USkeletalMesh*> Result;
// Clean up finished blood effects
for (int32 i = ActiveBloodEffects.Num() - 1; i >= 0; --i)
// Check if target mesh is valid
if (!TargetMesh)
{
if (!ActiveBloodEffects[i] || !ActiveBloodEffects[i]->IsActive())
return Result;
}
// Create cut plane mesh
UStaticMesh* CutPlaneMesh = CreateCutPlaneMesh(CutPlane);
if (!CutPlaneMesh)
{
ActiveBloodEffects.RemoveAt(i);
return Result;
}
// Use GeometryScript to perform boolean cut
// Note: This is just a framework, actual implementation requires GeometryScript API
// If bone name is specified, only cut the part influenced by the bone
if (BoneName != NAME_None)
{
// TODO: Get vertices influenced by the bone, only cut these vertices
}
// Create positive part skeletal mesh
USkeletalMesh* PositiveMesh = NewObject<USkeletalMesh>();
// TODO: Use GeometryScript to perform boolean difference operation for positive part
// Create negative part skeletal mesh
USkeletalMesh* NegativeMesh = NewObject<USkeletalMesh>();
// TODO: Use GeometryScript to perform boolean difference operation for negative part
// If cap creation is needed
if (bCreateCap)
{
// TODO: Create cap for positive and negative parts
}
// Add to result array
Result.Add(PositiveMesh);
Result.Add(NegativeMesh);
return Result;
}
// Perform boolean cut on procedural mesh
TArray<UProceduralMeshComponent*> UBooleanCutTool::CutProceduralMesh(UProceduralMeshComponent* TargetMesh, const FCutPlane& CutPlane, bool bCreateCap)
{
TArray<UProceduralMeshComponent*> Result;
// Check if target mesh is valid
if (!TargetMesh)
{
return Result;
}
// Get mesh data
TArray<FVector> Vertices;
TArray<int32> Triangles;
TArray<FVector> Normals;
TArray<FVector2D> UVs;
TArray<FColor> VertexColors;
TArray<FProcMeshTangent> Tangents;
// In UE5.5.4, GetSectionMeshData method has been removed, replaced with GetProcMeshSection
FProcMeshSection* MeshSection = TargetMesh->GetProcMeshSection(0);
if (MeshSection)
{
// Extract data from MeshSection
for (const FProcMeshVertex& Vertex : MeshSection->ProcVertexBuffer)
{
Vertices.Add(Vertex.Position);
Normals.Add(Vertex.Normal);
UVs.Add(Vertex.UV0);
VertexColors.Add(Vertex.Color);
Tangents.Add(Vertex.Tangent);
}
// Convert indices
for (uint32 Index : MeshSection->ProcIndexBuffer)
{
Triangles.Add(static_cast<int32>(Index));
}
}
// Calculate intersection points between cut plane and mesh
TArray<FVector> IntersectionPoints = CalculateIntersectionPoints(Vertices, Triangles, CutPlane);
// Create positive part procedural mesh
UProceduralMeshComponent* PositiveMesh = NewObject<UProceduralMeshComponent>(TargetMesh->GetOwner());
PositiveMesh->RegisterComponent();
// Create negative part procedural mesh
UProceduralMeshComponent* NegativeMesh = NewObject<UProceduralMeshComponent>(TargetMesh->GetOwner());
NegativeMesh->RegisterComponent();
// Split mesh
TArray<FVector> PositiveVertices;
TArray<int32> PositiveTriangles;
TArray<FVector> PositiveNormals;
TArray<FVector2D> PositiveUVs;
TArray<FColor> PositiveVertexColors;
TArray<FProcMeshTangent> PositiveTangents;
TArray<FVector> NegativeVertices;
TArray<int32> NegativeTriangles;
TArray<FVector> NegativeNormals;
TArray<FVector2D> NegativeUVs;
TArray<FColor> NegativeVertexColors;
TArray<FProcMeshTangent> NegativeTangents;
// TODO: Split mesh data based on cut plane
// Create positive part mesh
PositiveMesh->CreateMeshSection(0, PositiveVertices, PositiveTriangles, PositiveNormals, PositiveUVs, PositiveVertexColors, PositiveTangents, true);
// Create negative part mesh
NegativeMesh->CreateMeshSection(0, NegativeVertices, NegativeTriangles, NegativeNormals, NegativeUVs, NegativeVertexColors, NegativeTangents, true);
// If cap creation is needed
if (bCreateCap)
{
CreateCapMesh(IntersectionPoints, CutPlane, PositiveMesh);
CreateCapMesh(IntersectionPoints, CutPlane, NegativeMesh);
}
// Set material
if (CutMaterial)
{
PositiveMesh->SetMaterial(0, TargetMesh->GetMaterial(0));
PositiveMesh->SetMaterial(1, CutMaterial);
NegativeMesh->SetMaterial(0, TargetMesh->GetMaterial(0));
NegativeMesh->SetMaterial(1, CutMaterial);
}
// Add to result array
Result.Add(PositiveMesh);
Result.Add(NegativeMesh);
return Result;
}
void UBloodSystem::SpawnBloodEffect(const FVector& Location, const FVector& Direction, float Intensity)
// Create cut plane mesh
UStaticMesh* UBooleanCutTool::CreateCutPlaneMesh(const FCutPlane& CutPlane)
{
// Ensure we have a valid blood spray system
if (!BloodSpraySystem)
// Create a plane mesh
UStaticMesh* PlaneMesh = NewObject<UStaticMesh>();
// TODO: Use GeometryScript to create plane mesh
// Note: This is just a framework, actual implementation requires GeometryScript API
return PlaneMesh;
}
// Set cut material
void UBooleanCutTool::SetCutMaterial(UMaterialInterface* Material)
{
CutMaterial = Material;
}
// Get cut material
UMaterialInterface* UBooleanCutTool::GetCutMaterial() const
{
return CutMaterial;
}
// Calculate intersection points between cut plane and mesh
TArray<FVector> UBooleanCutTool::CalculateIntersectionPoints(const TArray<FVector>& Vertices, const TArray<int32>& Indices, const FCutPlane& CutPlane)
{
// Simplify the implementation of this method to resolve compilation issues
TArray<FVector> IntersectionPoints;
// TODO: Restore the complete implementation after resolving GeometryScripting issues
return IntersectionPoints;
}
// Create cap mesh
void UBooleanCutTool::CreateCapMesh(const TArray<FVector>& IntersectionPoints, const FCutPlane& CutPlane, UProceduralMeshComponent* TargetMesh)
{
// Check if there are enough intersection points
if (IntersectionPoints.Num() < 3)
{
return;
}
// Manage the number of active blood effects
if (ActiveBloodEffects.Num() >= MaxBloodEffects)
{
// Remove the oldest blood effect
if (ActiveBloodEffects[0])
{
ActiveBloodEffects[0]->DestroyComponent();
}
ActiveBloodEffects.RemoveAt(0);
}
// TODO: Create cap mesh using intersection points
// Note: This is just a framework, actual implementation requires more complex algorithm
// Create a rotation from the direction
FRotator Rotation = Direction.Rotation();
// Spawn the blood effect
UNiagaraComponent* BloodEffect = UNiagaraFunctionLibrary::SpawnSystemAtLocation(
GetWorld(),
BloodSpraySystem,
Location,
Rotation,
FVector(Intensity),
true,
true,
ENCPoolMethod::AutoRelease
);
if (BloodEffect)
{
// Set the intensity parameter if it exists
BloodEffect->SetFloatParameter(FName("Intensity"), Intensity);
// Add to active blood effects
ActiveBloodEffects.Add(BloodEffect);
}
}
AActor* UBloodSystem::CreateBloodPool(const FVector& Location, float Size)
{
// Ensure we have a valid blood pool class
if (!BloodPoolClass)
{
return nullptr;
}
// Manage the number of active blood pools
if (ActiveBloodPools.Num() >= MaxBloodPools)
{
// Remove the oldest blood pool
if (ActiveBloodPools[0])
{
ActiveBloodPools[0]->Destroy();
}
ActiveBloodPools.RemoveAt(0);
}
// Spawn the blood pool
FActorSpawnParameters SpawnParams;
SpawnParams.Owner = GetOwner();
SpawnParams.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AdjustIfPossibleButAlwaysSpawn;
// Trace downward to find the floor
FHitResult HitResult;
FVector TraceStart = Location;
FVector TraceEnd = Location - FVector(0, 0, 200.0f);
FCollisionQueryParams QueryParams;
QueryParams.AddIgnoredActor(GetOwner());
if (GetWorld()->LineTraceSingleByChannel(HitResult, TraceStart, TraceEnd, ECC_Visibility, QueryParams))
{
// Spawn at the hit location
AActor* BloodPool = GetWorld()->SpawnActor<AActor>(BloodPoolClass, HitResult.Location, FRotator::ZeroRotator, SpawnParams);
if (BloodPool)
{
// Set the size of the blood pool
BloodPool->SetActorScale3D(FVector(Size));
// Add to active blood pools
ActiveBloodPools.Add(BloodPool);
return BloodPool;
}
}
return nullptr;
}
UDecalComponent* UBloodSystem::ApplyBloodDecal(UPrimitiveComponent* Component, const FVector& Location, const FVector& Direction, float Size)
{
// Ensure we have a valid blood decal material
if (!BloodDecalMaterial || !Component)
{
return nullptr;
}
// Manage the number of active blood decals
if (ActiveBloodDecals.Num() >= MaxBloodDecals)
{
// Remove the oldest blood decal
if (ActiveBloodDecals[0])
{
ActiveBloodDecals[0]->DestroyComponent();
}
ActiveBloodDecals.RemoveAt(0);
}
// Create a rotation from the direction (opposite to the direction for the decal to face outward)
FRotator Rotation = (-Direction).Rotation();
// Create the decal component
UDecalComponent* DecalComponent = NewObject<UDecalComponent>(Component);
DecalComponent->SetupAttachment(Component);
DecalComponent->SetRelativeLocation(Component->GetComponentTransform().InverseTransformPosition(Location));
DecalComponent->SetRelativeRotation(Component->GetComponentTransform().InverseTransformRotation(Rotation.Quaternion()));
DecalComponent->SetDecalMaterial(BloodDecalMaterial);
DecalComponent->DecalSize = FVector(10.0f, Size * 100.0f, Size * 100.0f);
DecalComponent->RegisterComponent();
// Add to active blood decals
ActiveBloodDecals.Add(DecalComponent);
return DecalComponent;
// 1. Project intersection points onto cut plane
// 2. Triangulate projected points
// 3. Create cap mesh
// 4. Add to target mesh
}