feat: 实现各向异性形变系统和边缘约束

- Task 4: 实现方向检测辅助函数
  - FindNearestFreeEdgeDirection() - 查找最近的自由边界方向
  - CalculatePrimaryFlowDirection() - 计算材料流动的主方向
  - CalculateSecondaryFlowDirection() - 计算次要流动方向

- Task 5: 重构OnHammerImpact以支持各向异性形变
  - 添加材料流动方向计算
  - 在凸起区使用各向异性距离计算
  - 更新累积应变数组

- Task 6: 实现边缘顶点检测和约束
  - MarkEdgeVertices() - 检测并标记边缘顶点
  - ApplyEdgeLinearConstraint() - 对边缘顶点应用线性约束
  - 在OnHammerImpact中集成边缘约束逻辑

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-01-23 22:44:09 +08:00
parent bfb9be2256
commit 3c05d0f845
2 changed files with 225 additions and 23 deletions
+196 -23
View File
@@ -304,11 +304,14 @@ void ADynamicIronBlock::OnHammerImpact(FVector HitLocation, FVector HitNormal, f
// 计算实际的形变深度
float ActualDepth = ImpactDepth * ImpactForce;
// 计算凸起区域的外半径
float BulgeOuterRadius = ImpactRadius * (1.0f + BulgeRatio);
UE_LOG(LogTemp, Log, TEXT("DynamicIronBlock: Hammer impact at local position (%f, %f, %f), depth: %f, radius: %f, anisotropy: %f"),
LocalHitLocation.X, LocalHitLocation.Y, LocalHitLocation.Z, ActualDepth, ImpactRadius, AnisotropyRatio);
UE_LOG(LogTemp, Log, TEXT("DynamicIronBlock: Hammer impact at local position (%f, %f, %f), depth: %f, impact radius: %f, bulge radius: %f"),
LocalHitLocation.X, LocalHitLocation.Y, LocalHitLocation.Z, ActualDepth, ImpactRadius, BulgeOuterRadius);
// 计算材料流动方向
FVector BoxHalfSize = BoxSize * 0.5f;
FVector ToNearestEdge = FindNearestFreeEdgeDirection(LocalHitLocation, BoxHalfSize);
FVector PrimaryFlow = CalculatePrimaryFlowDirection(LocalHitNormal, ToNearestEdge);
FVector SecondaryFlow = CalculateSecondaryFlowDirection(LocalHitNormal, PrimaryFlow);
// 使用GeometryScript修改网格顶点
DynMesh->EditMesh([&](FDynamicMesh3& EditMesh)
@@ -323,9 +326,14 @@ void ADynamicIronBlock::OnHammerImpact(FVector HitLocation, FVector HitNormal, f
// 计算顶点到锤击点的距离
float Distance = FVector::Dist(VertexPosFloat, LocalHitLocation);
// 只处理影响范围内的顶点
float BulgeOuterRadius = ImpactRadius * (1.0f + BulgeRatio);
if (Distance >= BulgeOuterRadius)
continue;
FVector Displacement = FVector::ZeroVector;
// 压缩区:顶点在锤击半径内,向下压
// 压缩区:顶点在锤击半径内,向下压
if (Distance < ImpactRadius)
{
// 计算衰减因子(使用幂函数实现平滑过渡)
@@ -338,29 +346,44 @@ void ADynamicIronBlock::OnHammerImpact(FVector HitLocation, FVector HitNormal, f
// 沿着锤击法线方向进行形变(向内压)
Displacement = -LocalHitNormal * VertexDeformation;
}
// 凸起区:顶点在压缩区外、凸起区内,材料向上和向外流动
else if (Distance < BulgeOuterRadius)
// 凸起区:顶点在压缩区外、凸起区内,材料向上和向外流动
else
{
// 计算在凸起区中的位置 [0, 1]
float BulgeNormalizedDist = (Distance - ImpactRadius) / (BulgeOuterRadius - ImpactRadius);
// 将位置投影到流动方向
float DistAlongPrimary = FVector::DotProduct(
FVector(VertexPosFloat - LocalHitLocation), PrimaryFlow);
float DistAlongSecondary = FVector::DotProduct(
FVector(VertexPosFloat - LocalHitLocation), SecondaryFlow);
// 凸起区衰减:靠近压缩区边界最强,向外逐渐减弱
float BulgeFalloff = 1.0f - FMath::Pow(BulgeNormalizedDist, FalloffExponent);
// 各向异性距离计算
// 主方向的"有效距离"更小(更容易到达)→ 凸起更大
float EffectiveDistance = FMath::Sqrt(
FMath::Square(DistAlongPrimary / AnisotropyRatio) +
FMath::Square(DistAlongSecondary)
);
// 计算凸起高度(基于体积守恒和BulgeStrength)
float BulgeHeight = ActualDepth * BulgeStrength * BulgeFalloff;
float DistanceInBulgeZone = EffectiveDistance / (BulgeOuterRadius - ImpactRadius);
// 计算径向方向(从锤击点指向顶点的水平方向)
FVector RadialDir = VertexPosFloat - LocalHitLocation;
RadialDir = RadialDir - LocalHitNormal * FVector::DotProduct(RadialDir, LocalHitNormal); // 移除法线方向分量
RadialDir.Normalize();
if (DistanceInBulgeZone < 1.0f)
{
// 凸起区衰减:靠近压缩区边界最强,向外逐渐减弱
float BulgeFalloff = 1.0f - FMath::Pow(DistanceInBulgeZone, FalloffExponent);
// 材料流动:主要向上(反法线方向),少量向外(径向)
// 向上分量占70%,向外分量占30%
FVector UpwardDisplacement = -LocalHitNormal * BulgeHeight * 0.7f;
FVector OutwardDisplacement = RadialDir * BulgeHeight * 0.3f;
// 计算凸起高度(基于体积守恒和BulgeStrength)
float BulgeHeight = ActualDepth * BulgeStrength * BulgeFalloff;
Displacement = UpwardDisplacement + OutwardDisplacement;
// 材料流动:主要向上(反法线方向),少量向外(径向)
// 向上分量占70%,向外分量占30%
FVector UpwardDisplacement = -LocalHitNormal * BulgeHeight * 0.7f;
// 计算径向方向(各向异性)
FVector RadialDir = PrimaryFlow * DistAlongPrimary + SecondaryFlow * DistAlongSecondary;
RadialDir.Normalize();
FVector OutwardDisplacement = RadialDir * BulgeHeight * 0.3f;
Displacement = UpwardDisplacement + OutwardDisplacement;
}
}
// 应用形变
@@ -368,6 +391,43 @@ void ADynamicIronBlock::OnHammerImpact(FVector HitLocation, FVector HitNormal, f
{
FVector3d NewPosition = VertexPos + FVector3d(Displacement.X, Displacement.Y, Displacement.Z);
EditMesh.SetVertex(VertexID, NewPosition);
// 更新累积应变(累加位移量)
if (bVertexAttributesInitialized && AccumulatedStrainArray.IsValidIndex(VertexID))
{
float CurrentStrain = AccumulatedStrainArray[VertexID];
float StrainDelta = Displacement.Size();
AccumulatedStrainArray[VertexID] = CurrentStrain + StrainDelta;
}
}
}
// 边缘约束(如果启用)
if (bEnableEdgeConstraint)
{
TSet<int32> EdgeVertices;
MarkEdgeVertices(EditMesh, EdgeVertices);
// 只处理受影响区域的边缘顶点
TSet<int32> AffectedEdges;
float BulgeOuterRadius = ImpactRadius * (1.0f + BulgeRatio);
for (int32 VID : EdgeVertices)
{
if (!EditMesh.IsVertex(VID))
continue;
FVector3d Pos = EditMesh.GetVertex(VID);
FVector PosFloat(Pos.X, Pos.Y, Pos.Z);
if (FVector::Dist(PosFloat, LocalHitLocation) < ImpactRadius * 1.5f)
{
AffectedEdges.Add(VID);
}
}
if (AffectedEdges.Num() > 0)
{
ApplyEdgeLinearConstraint(EditMesh, AffectedEdges);
UE_LOG(LogTemp, Log, TEXT("DynamicIronBlock: Applied edge constraint to %d vertices"), AffectedEdges.Num());
}
}
}, EDynamicMeshChangeType::GeneralEdit, EDynamicMeshAttributeChangeFlags::Unknown, false);
@@ -382,7 +442,7 @@ void ADynamicIronBlock::OnHammerImpact(FVector HitLocation, FVector HitNormal, f
// 重新生成 BVH
GenerateBVH();
UE_LOG(LogTemp, Log, TEXT("DynamicIronBlock: Hammer impact deformation applied successfully"));
UE_LOG(LogTemp, Log, TEXT("DynamicIronBlock: Anisotropic hammer impact applied successfully"));
}
// ========== 温度系统实现 ==========
@@ -1367,3 +1427,116 @@ void ADynamicIronBlock::ReleaseVertexAttributes()
UE_LOG(LogTemp, Log, TEXT("DynamicIronBlock: Vertex attributes released"));
}
// ========== 各向异性形变辅助函数实现 ==========
FVector ADynamicIronBlock::FindNearestFreeEdgeDirection(const FVector& LocalPos, const FVector& BoxHalfSize) const
{
// 计算到6个面的距离
float DistToEdges[6] = {
BoxHalfSize.X + LocalPos.X, // -X面
BoxHalfSize.X - LocalPos.X, // +X面
BoxHalfSize.Y + LocalPos.Y, // -Y面
BoxHalfSize.Y - LocalPos.Y, // +Y面
BoxHalfSize.Z + LocalPos.Z, // -Z面
BoxHalfSize.Z - LocalPos.Z // +Z面
};
// 找最近的边缘
int32 NearestEdge = 0;
float MinDist = DistToEdges[0];
for (int32 i = 1; i < 6; ++i)
{
if (DistToEdges[i] < MinDist)
{
MinDist = DistToEdges[i];
NearestEdge = i;
}
}
// 返回朝向最近边缘的方向
static const FVector EdgeDirections[6] = {
FVector(-1, 0, 0), FVector(1, 0, 0),
FVector(0, -1, 0), FVector(0, 1, 0),
FVector(0, 0, -1), FVector(0, 0, 1)
};
return EdgeDirections[NearestEdge];
}
FVector ADynamicIronBlock::CalculatePrimaryFlowDirection(const FVector& HammerDir, const FVector& ToNearestEdge) const
{
// 移除锤击方向的分量(材料不能向锤头方向流动)
FVector PrimaryFlow = ToNearestEdge - HammerDir * FVector::DotProduct(ToNearestEdge, HammerDir);
return PrimaryFlow.GetSafeNormal();
}
FVector ADynamicIronBlock::CalculateSecondaryFlowDirection(const FVector& HammerDir, const FVector& PrimaryFlow) const
{
return FVector::CrossProduct(HammerDir, PrimaryFlow).GetSafeNormal();
}
void ADynamicIronBlock::MarkEdgeVertices(const FDynamicMesh3& Mesh, TSet<int32>& OutEdgeVertices) const
{
FVector BoxHalfSize = BoxSize * 0.5f;
float EdgeThresholdCM = EdgeThreshold;
for (int32 VID : Mesh.VertexIndicesItr())
{
FVector3d Pos = Mesh.GetVertex(VID);
FVector PosFloat(Pos.X, Pos.Y, Pos.Z);
// 检查是否接近任何边界
bool bNearEdge = false;
bNearEdge |= FMath::Abs(PosFloat.X - BoxHalfSize.X) < EdgeThresholdCM;
bNearEdge |= FMath::Abs(PosFloat.X + BoxHalfSize.X) < EdgeThresholdCM;
bNearEdge |= FMath::Abs(PosFloat.Y - BoxHalfSize.Y) < EdgeThresholdCM;
bNearEdge |= FMath::Abs(PosFloat.Y + BoxHalfSize.Y) < EdgeThresholdCM;
bNearEdge |= FMath::Abs(PosFloat.Z - BoxHalfSize.Z) < EdgeThresholdCM;
bNearEdge |= FMath::Abs(PosFloat.Z + BoxHalfSize.Z) < EdgeThresholdCM;
if (bNearEdge)
{
OutEdgeVertices.Add(VID);
}
}
}
void ADynamicIronBlock::ApplyEdgeLinearConstraint(FDynamicMesh3& Mesh, const TSet<int32>& EdgeVertices)
{
// 简化版本:仅平滑边缘顶点,不进行完整的PCA拟合
for (int32 VID : EdgeVertices)
{
if (!Mesh.IsVertex(VID))
continue;
// 找到此顶点的边缘邻居
TArray<int32> EdgeNeighbors;
for (int32 NbrVID : Mesh.VtxVerticesItr(VID))
{
if (EdgeVertices.Contains(NbrVID))
{
EdgeNeighbors.Add(NbrVID);
}
}
if (EdgeNeighbors.Num() >= 2)
{
// 计算邻居顶点的平均位置作为目标位置
FVector3f AvgPos(0.0f, 0.0f, 0.0f);
for (int32 NbrVID : EdgeNeighbors)
{
AvgPos += FVector3f(Mesh.GetVertex(NbrVID));
}
AvgPos /= EdgeNeighbors.Num();
// 当前位置
FVector3d CurrentPos = Mesh.GetVertex(VID);
// 平滑更新(50%向平均位置移动)
FVector3d SmoothPos = FMath::Lerp(CurrentPos, FVector3d(AvgPos.X, AvgPos.Y, AvgPos.Z), 0.5f);
Mesh.SetVertex(VID, SmoothPos);
}
}
}
+29
View File
@@ -312,6 +312,35 @@ private:
*/
void ReleaseVertexAttributes();
// ========== 各向异性形变辅助函数 ==========
/**
* 查找最近的自由边界方向
* 材料会优先朝向自由边界流动(阻力最小)
*/
FVector FindNearestFreeEdgeDirection(const FVector& LocalPos, const FVector& BoxHalfSize) const;
/**
* 计算材料流动的主方向
* 主方向 = 朝向边缘 + 垂直于锤击方向
*/
FVector CalculatePrimaryFlowDirection(const FVector& HammerDir, const FVector& ToNearestEdge) const;
/**
* 计算次要流动方向(垂直于主方向和锤击方向)
*/
FVector CalculateSecondaryFlowDirection(const FVector& HammerDir, const FVector& PrimaryFlow) const;
/**
* 检测并标记边缘顶点
*/
void MarkEdgeVertices(const FDynamicMesh3& Mesh, TSet<int32>& OutEdgeVertices) const;
/**
* 对边缘顶点应用线性约束
*/
void ApplyEdgeLinearConstraint(FDynamicMesh3& Mesh, const TSet<int32>& EdgeVertices);
// ========== GPU计算系统(Phase 2) ==========
// GPU缓冲区(Ping-Pong双缓冲)