原 docs/plans/ 下的 10 份混合文档(需求+设计混杂)归档到 docs/_archive/plans/ 作为决策考古用途。hammer-deformation-enhancement 实施清单迁移到 docs/impl/。后续工作产出统一进入 docs/requirements/ 与 docs/design/ 两个清晰分层。 Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
38 KiB
锤击形变系统增强实施计划
For Claude: REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
Goal: 实现各向异性材料流动、顶点属性系统(打磨度、初始位置)、表面磨损可视化和基础网格质量维护
Architecture: 混合体素-顶点架构,体素存储全局物理场(温度),顶点存储局部材料历史(应变、硬度、打磨度)
Tech Stack: Unreal Engine 5.6 C++, GeometryScripting API, DynamicMesh3, 自定义顶点属性层
Task 1: 添加顶点属性层基础结构
Files:
- Modify:
Source/BladeSmith/DynamicIronBlock.h:44-60
Step 1: 在头文件中添加顶点属性声明
在 ADynamicIronBlock 类的 private 部分添加:
// ========== 顶点属性层(阶段1) ==========
// 顶点属性层 - 使用FDynamicMesh3的扩展属性系统
TSharedPtr<TDynamicMeshVertexAttribute<FVector<float>>> InitialPositions; // 初始位置(不变,用于无漂移纹理采样)
TSharedPtr<TDynamicMeshVertexAttribute<FVector<float>>> AccumulatedStrain; // 累积塑性应变
TSharedPtr<TDynamicMeshVertexAttribute<FVector<float>>> HardnessCoefficient; // 硬度系数(影响形变阻力)
TSharedPtr<TDynamicMeshVertexAttribute<FVector<float>>> PolishAmount; // 打磨度(0=生锈,1=光亮)
Step 2: 添加属性初始化函数声明
在头文件的 private 部分添加函数声明:
/**
* 初始化顶点属性层
*/
void InitializeVertexAttributes();
/**
* 释放顶点属性层
*/
void ReleaseVertexAttributes();
Step 3: 在 OnConstruction 中调用初始化
在 Source/BladeSmith/DynamicIronBlock.cpp:236 之后添加:
// 初始化顶点属性层
InitializeVertexAttributes();
Step 4: 在 EndPlay 中调用释放
在 Source/BladeSmith/DynamicIronBlock.cpp:119 之前添加:
// 释放顶点属性层
ReleaseVertexAttributes();
Step 5: 编译检查
Run: 在 Visual Studio 中编译项目
Expected: 编译成功,无错误
Commit: git add Source/BladeSmith/DynamicIronBlock.h Source/BladeSmith/DynamicIronBlock.cpp && git commit -m "feat: 添加顶点属性层基础结构"
Task 2: 实现顶点属性初始化
Files:
- Modify:
Source/BladeSmith/DynamicIronBlock.cpp:990
Step 1: 实现初始化函数
在文件末尾添加:
void ADynamicIronBlock::InitializeVertexAttributes()
{
UDynamicMeshComponent* DynMeshComp = GetDynamicMeshComponent();
if (!DynMeshComp)
{
UE_LOG(LogTemp, Error, TEXT("DynamicIronBlock: Failed to get DynamicMeshComponent for vertex attributes"));
return;
}
UDynamicMesh* DynMesh = DynMeshComp->GetDynamicMesh();
if (!DynMesh)
{
UE_LOG(LogTemp, Error, TEXT("DynamicIronBlock: Failed to get DynamicMesh for vertex attributes"));
return;
}
// 读取网格以访问底层FDynamicMesh3
DynMesh->ProcessMesh([&](const FDynamicMesh3& ReadMesh)
{
// 注意:FDynamicMesh3是const的,我们需要在EditMesh中创建属性
// 这里仅获取顶点数量用于预分配
int32 VertexCount = ReadMesh.VertexCount();
UE_LOG(LogTemp, Log, TEXT("DynamicIronBlock: Initializing vertex attributes for %d vertices"), VertexCount);
});
// 使用EditMesh创建属性层
DynMesh->EditMesh([&](FDynamicMesh3& EditMesh)
{
// 创建初始位置属性
InitialPositions = MakeShared<TDynamicMeshVertexAttribute<FVector<float>>>(EditMesh);
InitialPositions->Initialize("InitialPosition");
// 创建累积应变属性
AccumulatedStrain = MakeShared<TDynamicMeshVertexAttribute<FVector<float>>>(EditMesh);
AccumulatedStrain->Initialize("AccumulatedStrain");
// 创建硬度系数属性
HardnessCoefficient = MakeShared<TDynamicMeshVertexAttribute<FVector<float>>>(EditMesh);
HardnessCoefficient->Initialize("HardnessCoefficient");
// 创建打磨度属性
PolishAmount = MakeShared<TDynamicMeshVertexAttribute<FVector<float>>>(EditMesh);
PolishAmount->Initialize("PolishAmount");
// 初始化所有顶点的属性值
for (int32 VertexID : EditMesh.VertexIndicesItr())
{
FVector3d VertexPos = EditMesh.GetVertex(VertexID);
// 初始位置 = 当前位置
InitialPositions->SetValue(VertexID, FVector3f(VertexPos.X, VertexPos.Y, VertexPos.Z));
// 累积应变 = 0
AccumulatedStrain->SetValue(VertexID, 0.0f);
// 硬度系数 = 1.0 (未硬化)
HardnessCoefficient->SetValue(VertexID, 1.0f);
// 打磨度 = 0.2 (轻微生锈)
PolishAmount->SetValue(VertexID, 0.2f);
}
}, EDynamicMeshChangeType::GeneralEdit);
UE_LOG(LogTemp, Log, TEXT("DynamicIronBlock: Vertex attributes initialized successfully"));
}
Step 2: 实现释放函数
在文件末尾添加:
void ADynamicIronBlock::ReleaseVertexAttributes()
{
InitialPositions.Reset();
AccumulatedStrain.Reset();
HardnessCoefficient.Reset();
PolishAmount.Reset();
UE_LOG(LogTemp, Log, TEXT("DynamicIronBlock: Vertex attributes released"));
}
Step 3: 编译并测试
Run: 在UE5编辑器中启动游戏
Expected: 在Output Log中看到 "Vertex attributes initialized successfully"
Commit: git add Source/BladeSmith/DynamicIronBlock.cpp && git commit -m "feat: 实现顶点属性初始化"
Task 3: 添加各向异性流动参数
Files:
- Modify:
Source/BladeSmith/DynamicIronBlock.h:75-76
Step 1: 添加可编辑参数
在材质属性区域添加:
// 各向异性比(主方向/次方向流动强度比,1.0=均匀,>1.0=方向性)
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Iron Block|Material Properties", meta = (ClampMin = "1.0", ClampMax = "10.0"))
float AnisotropyRatio = 3.0f;
// 是否启用边缘约束
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Iron Block|Material Properties")
bool bEnableEdgeConstraint = false;
// 边缘阈值(cm)- 用于检测边缘顶点
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Iron Block|Material Properties", meta = (ClampMin = "0.1", ClampMax = "5.0"))
float EdgeThreshold = 0.5f;
Step 2: 编译检查
Run: 在Visual Studio中编译
Expected: 编译成功
Commit: git add Source/BladeSmith/DynamicIronBlock.h && git commit -m "feat: 添加各向异性流动参数"
Task 4: 实现方向检测辅助函数
Files:
- Modify:
Source/BladeSmith/DynamicIronBlock.h:331
Step 1: 添加函数声明
在头文件 private 部分添加:
/**
* 查找最近的自由边界方向
* 材料会优先朝向自由边界流动(阻力最小)
*/
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;
Step 2: 实现方向检测函数
在 Source/BladeSmith/DynamicIronBlock.cpp:1297 之后添加:
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();
}
Step 3: 编译检查
Run: 在Visual Studio中编译
Expected: 编译成功
Commit: git add Source/BladeSmith/DynamicIronBlock.h Source/BladeSmith/DynamicIronBlock.cpp && git commit -m "feat: 实现方向检测辅助函数"
Task 5: 重构OnHammerImpact以支持各向异性形变
Files:
- Modify:
Source/BladeSmith/DynamicIronBlock.cpp:272-380
Step 1: 备份原函数
先将现有函数注释掉,保留作为参考
Step 2: 重写OnHammerImpact主流程
void ADynamicIronBlock::OnHammerImpact(FVector HitLocation, FVector HitNormal, float ImpactRadius, float ImpactDepth, float ImpactForce)
{
// 获取 DynamicMeshComponent
UDynamicMeshComponent* DynMeshComp = GetDynamicMeshComponent();
if (!DynMeshComp)
{
UE_LOG(LogTemp, Error, TEXT("DynamicIronBlock: Failed to get DynamicMeshComponent for hammer impact"));
return;
}
// 获取 DynamicMesh
UDynamicMesh* DynMesh = DynMeshComp->GetDynamicMesh();
if (!DynMesh)
{
UE_LOG(LogTemp, Error, TEXT("DynamicIronBlock: Failed to get DynamicMesh for hammer impact"));
return;
}
// 限制力度范围在0-1之间
ImpactForce = FMath::Clamp(ImpactForce, 0.0f, 1.0f);
// 将世界坐标转换为局部坐标
FTransform ActorTransform = GetActorTransform();
FVector LocalHitLocation = ActorTransform.InverseTransformPosition(HitLocation);
FVector LocalHitNormal = ActorTransform.InverseTransformVector(HitNormal).GetSafeNormal();
// 计算实际的形变深度
float ActualDepth = ImpactDepth * ImpactForce;
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);
// 计算材料流动方向
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)
{
// 遍历所有顶点
for (int32 VertexID : EditMesh.VertexIndicesItr())
{
// 获取顶点位置
FVector3d VertexPos = EditMesh.GetVertex(VertexID);
FVector VertexPosFloat(VertexPos.X, VertexPos.Y, VertexPos.Z);
// 计算顶点到锤击点的距离
float Distance = FVector::Dist(VertexPosFloat, LocalHitLocation);
// 只处理影响范围内的顶点
float BulgeOuterRadius = ImpactRadius * (1.0f + BulgeRatio);
if (Distance >= BulgeOuterRadius)
continue;
FVector Displacement = FVector::ZeroVector;
// 压缩区:顶点在锤击半径内,向下压
if (Distance < ImpactRadius)
{
// 计算衰减因子(使用幂函数实现平滑过渡)
float NormalizedDist = Distance / ImpactRadius; // 归一化距离 [0, 1]
float Falloff = 1.0f - FMath::Pow(NormalizedDist, FalloffExponent); // 衰减系数
// 计算该顶点的形变偏移量
float VertexDeformation = ActualDepth * Falloff;
// 沿着锤击法线方向进行形变(向内压)
Displacement = -LocalHitNormal * VertexDeformation;
}
// 凸起区:顶点在压缩区外、凸起区内,材料向上和向外流动
else
{
// 将位置投影到流动方向
float DistAlongPrimary = FVector::DotProduct(
FVector(VertexPosFloat - LocalHitLocation), PrimaryFlow);
float DistAlongSecondary = FVector::DotProduct(
FVector(VertexPosFloat - LocalHitLocation), SecondaryFlow);
// 各向异性距离计算
// 主方向的"有效距离"更小(更容易到达)→ 凸起更大
float EffectiveDistance = FMath::Sqrt(
FMath::Square(DistAlongPrimary / AnisotropyRatio) +
FMath::Square(DistAlongSecondary)
);
float DistanceInBulgeZone = EffectiveDistance / (BulgeOuterRadius - ImpactRadius);
if (DistanceInBulgeZone < 1.0f)
{
// 凸起区衰减:靠近压缩区边界最强,向外逐渐减弱
float BulgeFalloff = 1.0f - FMath::Pow(DistanceInBulgeZone, FalloffExponent);
// 计算凸起高度(基于体积守恒和BulgeStrength)
float BulgeHeight = ActualDepth * BulgeStrength * BulgeFalloff;
// 材料流动:主要向上(反法线方向),少量向外(径向)
// 向上分量占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;
}
}
// 应用形变
if (!Displacement.IsNearlyZero())
{
FVector3d NewPosition = VertexPos + FVector3d(Displacement.X, Displacement.Y, Displacement.Z);
EditMesh.SetVertex(VertexID, NewPosition);
// 更新累积应变(累加位移量)
if (AccumulatedStrain.IsValid())
{
float CurrentStrain = AccumulatedStrain->GetValue(VertexID);
float StrainDelta = Displacement.Size();
AccumulatedStrain->SetValue(VertexID, CurrentStrain + StrainDelta);
}
}
}
}, EDynamicMeshChangeType::GeneralEdit, EDynamicMeshAttributeChangeFlags::Unknown, false);
// 重新计算法线
FGeometryScriptCalculateNormalsOptions NormalOp;
UGeometryScriptLibrary_MeshNormalsFunctions::RecomputeNormals(DynMesh, NormalOp);
// 通知组件网格已更新
DynMeshComp->NotifyMeshUpdated();
// 重新生成 BVH
GenerateBVH();
UE_LOG(LogTemp, Log, TEXT("DynamicIronBlock: Anisotropic hammer impact applied successfully"));
}
Step 3: 编译并测试
Run: 在UE5编辑器中启动游戏,执行锤击
Expected: 锤击后材料沿边缘方向流动更明显
Commit: git add Source/BladeSmith/DynamicIronBlock.cpp && git commit -m "feat: 实现各向异性材料流动形变"
Task 6: 实现边缘顶点检测和约束
Files:
- Modify:
Source/BladeSmith/DynamicIronBlock.h:331
Step 1: 添加函数声明
在头文件 private 部分添加:
/**
* 检测并标记边缘顶点
*/
void MarkEdgeVertices(const FDynamicMesh3& Mesh, TSet<int32>& OutEdgeVertices) const;
/**
* 对边缘顶点应用线性约束
*/
void ApplyEdgeLinearConstraint(FDynamicMesh3& Mesh, const TSet<int32>& EdgeVertices);
Step 2: 实现边缘检测
在 Source/BladeSmith/DynamicIronBlock.cpp:1450 之后添加:
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);
}
}
}
Step 3: 在OnHammerImpact中集成边缘约束
在Task 5的 EditMesh 结束后、RecomputeNormals 之前添加:
// 边缘约束(如果启用)
if (bEnableEdgeConstraint)
{
TSet<int32> EdgeVertices;
MarkEdgeVertices(EditMesh, EdgeVertices);
// 只处理受影响区域的边缘顶点
TSet<int32> AffectedEdges;
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());
}
}
Step 4: 编译并测试
Run: 在UE5编辑器中测试,启用 bEnableEdgeConstraint
Expected: 边缘顶点保持更平整
Commit: git add Source/BladeSmith/DynamicIronBlock.h Source/BladeSmith/DynamicIronBlock.cpp && git commit -m "feat: 实现边缘顶点约束"
Task 7: 实现打磨度在锤击时的更新
Files:
- Modify:
Source/BladeSmith/DynamicIronBlock.cpp:420
Step 1: 在OnHammerImpact的EditMesh中更新打磨度
在Task 5中应用形变的位置后添加:
// 更新打磨度(锤击使表面变亮)
if (PolishAmount.IsValid())
{
float CurrentPolish = PolishAmount->GetValue(VertexID);
// 打磨度增加量与形变量成正比
float PolishIncrease = Displacement.Size() * 0.5f;
float NewPolish = FMath::Clamp(CurrentPolish + PolishIncrease, 0.0f, 1.0f);
PolishAmount->SetValue(VertexID, NewPolish);
}
Step 2: 编译并测试
Run: 锤击后检查打磨度是否增加
Commit: git add Source/BladeSmith/DynamicIronBlock.cpp && git commit -m "feat: 实现锤击时的打磨度更新"
Task 8: 更新顶点颜色以编码打磨度
Files:
- Modify:
Source/BladeSmith/DynamicIronBlock.cpp:812
Step 1: 修改UpdateVertexColors以支持打磨度
找到现有的 UpdateVertexColors 函数,修改颜色计算部分:
void ADynamicIronBlock::UpdateVertexColors()
{
if (!bEnableTemperatureSystem || TemperatureGrid.Num() == 0)
{
return;
}
// 获取 DynamicMeshComponent
UDynamicMeshComponent* DynMeshComp = GetDynamicMeshComponent();
if (!DynMeshComp)
{
UE_LOG(LogTemp, Warning, TEXT("DynamicIronBlock: Cannot update vertex colors, DynamicMeshComponent is null"));
return;
}
// 获取 DynamicMesh
UDynamicMesh* DynMesh = DynMeshComp->GetDynamicMesh();
if (!DynMesh)
{
UE_LOG(LogTemp, Warning, TEXT("DynamicIronBlock: Cannot update vertex colors, DynamicMesh is null"));
return;
}
// 准备颜色列表
TArray<FLinearColor> VertexColors;
int32 VertexCount = 0;
// 读取网格以获取顶点信息并计算颜色
DynMesh->ProcessMesh([&](const FDynamicMesh3& ReadMesh)
{
VertexCount = ReadMesh.MaxVertexID();
// 预分配颜色数组大小
VertexColors.Reserve(VertexCount);
for (int32 VertexID = 0; VertexID < VertexCount; ++VertexID)
{
if (!ReadMesh.IsVertex(VertexID))
{
VertexColors.Add(FLinearColor::Black);
continue;
}
// 获取顶点位置(局部坐标)
FVector3d VertexPos = ReadMesh.GetVertex(VertexID);
FVector VertexPosFloat(VertexPos.X, VertexPos.Y, VertexPos.Z);
// 采样该位置的温度
float Temperature = SampleTemperatureAtPosition(VertexPosFloat);
// 将温度转换为黑体辐射颜色
FLinearColor TempColor = TemperatureToColor(Temperature);
// 获取打磨度并混合颜色
float Polish = 0.2f; // 默认值
if (PolishAmount.IsValid())
{
Polish = PolishAmount->GetValue(VertexID);
}
// 生锈颜色(深红褐色)
FLinearColor RustColor(0.4f, 0.2f, 0.15f);
// 金属颜色(亮银色)
FLinearColor MetalColor(0.9f, 0.9f, 0.95f);
// 根据打磨度混合:0=生锈,1=光亮金属
FLinearColor PolishColor = FMath::Lerp(RustColor, MetalColor, Polish);
// 最终颜色 = 温度颜色 * 打磨颜色(乘法混合)
FLinearColor FinalColor = TempColor * PolishColor;
FinalColor.A = 1.0f; // 不透明
VertexColors.Add(FinalColor);
}
});
// 创建 FGeometryScriptColorList 并关联颜色数组
FGeometryScriptColorList ColorList;
ColorList.List = MakeShared<TArray<FLinearColor>>(MoveTemp(VertexColors));
// 使用 GeometryScript 设置顶点颜色
UGeometryScriptLibrary_MeshVertexColorFunctions::SetMeshPerVertexColors(
DynMesh,
ColorList
);
// 通知组件网格已更新
DynMeshComp->NotifyMeshUpdated();
// 强制刷新渲染状态以确保顶点颜色更新
DynMeshComp->MarkRenderStateDirty();
DynMeshComp->MarkRenderDynamicDataDirty();
UE_LOG(LogTemp, Log, TEXT("DynamicIronBlock: Vertex colors updated with polish information"));
}
Step 2: 编译并测试
Run: 锤击后观察表面颜色变化
Expected: 锤击区域从红褐色变为银色
Commit: git add Source/BladeSmith/DynamicIronBlock.cpp && git commit -m "feat: 更新顶点颜色以显示打磨度"
Task 9: 添加基础Laplacian平滑
Files:
- Modify:
Source/BladeSmith/DynamicIronBlock.h:331
Step 1: 添加函数声明
/**
* 应用Laplacian平滑到受影响区域
*/
void ApplyLaplacianSmoothing(FDynamicMesh3& Mesh, const FVector& Center, float Radius, float Alpha = 0.3f);
Step 2: 实现平滑函数
在 Source/BladeSmith/DynamicIronBlock.cpp:1500 之后添加:
void ADynamicIronBlock::ApplyLaplacianSmoothing(FDynamicMesh3& Mesh, const FVector& Center, float Radius, float Alpha)
{
// 创建位置副本用于迭代计算
TMap<int32, FVector3d> NewPositions;
for (int32 VertexID : Mesh.VertexIndicesItr())
{
FVector3d VertexPos = Mesh.GetVertex(VertexID);
FVector PosFloat(VertexPos.X, VertexPos.Y, VertexPos.Z);
// 只处理影响范围内的顶点
if (FVector::Dist(PosFloat, Center) > Radius)
continue;
// 计算Laplacian(邻居顶点的平均位置)
FVector3d Laplacian(0.0, 0.0, 0.0);
int32 NeighborCount = 0;
for (int32 NbrVID : Mesh.VtxVerticesItr(VertexID))
{
Laplacian += Mesh.GetVertex(NbrVID);
NeighborCount++;
}
if (NeighborCount > 0)
{
Laplacian /= NeighborCount;
// 平滑更新:新位置 = 原位置 + Alpha * (Laplacian - 原位置)
FVector3d SmoothPos = FMath::Lerp(VertexPos, Laplacian, Alpha);
NewPositions.Add(VertexID, SmoothPos);
}
}
// 应用新位置
for (const TPair<int32, FVector3d>& Pair : NewPositions)
{
Mesh.SetVertex(Pair.Key, Pair.Value);
}
}
Step 3: 在OnHammerImpact中调用平滑
在Task 6的边缘约束之后添加:
// 应用Laplacian平滑(轻微,保持形状细节)
ApplyLaplacianSmoothing(EditMesh, LocalHitLocation, BulgeOuterRadius, 0.1f);
Step 4: 编译并测试
Run: 连续锤击50次,检查网格质量
Expected: 网格保持均匀,无严重退化
Commit: git add Source/BladeSmith/DynamicIronBlock.h Source/BladeSmith/DynamicIronBlock.cpp && git commit -m "feat: 添加Laplacian平滑以维护网格质量"
Task 10: 添加调试可视化工具
Files:
- Modify:
Source/BladeSmith/DynamicIronBlock.h:148
Step 1: 添加调试函数声明
/**
* 调试函数 - 可视化材料流动方向
*/
UFUNCTION(BlueprintCallable, Category = "Hammer Impact|Debug")
void DebugDrawFlowDirections();
/**
* 调试函数 - 可视化边缘顶点
*/
UFUNCTION(BlueprintCallable, Category = "Hammer Impact|Debug")
void DebugDrawEdgeVertices();
/**
* 调试函数 - 输出顶点属性统计信息
*/
UFUNCTION(BlueprintCallable, Category = "Hammer Impact|Debug")
void DebugPrintVertexAttributes();
Step 2: 实现调试函数
在 Source/BladeSmith/DynamicIronBlock.cpp:1550 之后添加:
void ADynamicIronBlock::DebugDrawFlowDirections()
{
if (!GetWorld())
return;
UDynamicMeshComponent* DynMeshComp = GetDynamicMeshComponent();
if (!DynMeshComp)
return;
UDynamicMesh* DynMesh = DynMeshComp->GetDynamicMesh();
if (!DynMesh)
return;
FVector BoxHalfSize = BoxSize * 0.5f;
// 在网格表面采样点
DynMesh->ProcessMesh([&](const FDynamicMesh3& ReadMesh)
{
int32 SampleCount = 0;
for (int32 VertexID : ReadMesh.VertexIndicesItr())
{
// 每100个顶点采样一次
if (SampleCount++ % 100 != 0)
continue;
FVector3d Pos3d = ReadMesh.GetVertex(VertexID);
FVector Pos(Pos3d.X, Pos3d.Y, Pos3d.Z);
// 计算流动方向
FVector ToNearestEdge = FindNearestFreeEdgeDirection(Pos, BoxHalfSize);
FVector PrimaryFlow = CalculatePrimaryFlowDirection(FVector::DownVector, ToNearestEdge);
// 转换到世界坐标
FVector WorldPos = GetActorTransform().TransformPosition(Pos);
FVector WorldDir = GetActorTransform().TransformVector(PrimaryFlow);
// 绘制方向箭头
DrawDebugDirectionalArrow(
GetWorld(),
WorldPos,
WorldPos + WorldDir * 5.0f,
100.0f, // 箭头大小
FColor::Cyan,
false, // 是否持久
5.0f, // 持续时间
0, // 深度优先级
0.5f // 箭头厚度
);
}
});
UE_LOG(LogTemp, Warning, TEXT("DynamicIronBlock: Flow direction debug visualization drawn"));
}
void ADynamicIronBlock::DebugDrawEdgeVertices()
{
if (!GetWorld())
return;
UDynamicMeshComponent* DynMeshComp = GetDynamicMeshComponent();
if (!DynMeshComp)
return;
UDynamicMesh* DynMesh = DynMeshComp->GetDynamicMesh();
if (!DynMesh)
return;
TSet<int32> EdgeVertices;
DynMesh->ProcessMesh([&](const FDynamicMesh3& ReadMesh)
{
MarkEdgeVertices(ReadMesh, EdgeVertices);
});
for (int32 VID : EdgeVertices)
{
DynMesh->ProcessMesh([&](const FDynamicMesh3& ReadMesh)
{
if (!ReadMesh.IsVertex(VID))
return;
FVector3d Pos3d = ReadMesh.GetVertex(VID);
FVector WorldPos = GetActorTransform().TransformPosition(FVector(Pos3d.X, Pos3d.Y, Pos3d.Z));
DrawDebugPoint(GetWorld(), WorldPos, 8.0f, FColor::Red, false, 5.0f);
});
}
UE_LOG(LogTemp, Warning, TEXT("DynamicIronBlock: Edge vertices debug visualization drawn (%d vertices)"), EdgeVertices.Num());
}
void ADynamicIronBlock::DebugPrintVertexAttributes()
{
UDynamicMeshComponent* DynMeshComp = GetDynamicMeshComponent();
if (!DynMeshComp)
return;
UDynamicMesh* DynMesh = DynMeshComp->GetDynamicMesh();
if (!DynMesh)
return;
if (!AccumulatedStrain.IsValid() || !PolishAmount.IsValid())
{
UE_LOG(LogTemp, Warning, TEXT("DynamicIronBlock: Vertex attributes not initialized"));
return;
}
// 统计信息
float MinStrain = FLT_MAX, MaxStrain = -FLT_MAX, AvgStrain = 0.0f;
float MinPolish = FLT_MAX, MaxPolish = -FLT_MAX, AvgPolish = 0.0f;
int32 Count = 0;
DynMesh->ProcessMesh([&](const FDynamicMesh3& ReadMesh)
{
for (int32 VertexID : ReadMesh.VertexIndicesItr())
{
float Strain = AccumulatedStrain->GetValue(VertexID);
float Polish = PolishAmount->GetValue(VertexID);
MinStrain = FMath::Min(MinStrain, Strain);
MaxStrain = FMath::Max(MaxStrain, Strain);
AvgStrain += Strain;
MinPolish = FMath::Min(MinPolish, Polish);
MaxPolish = FMath::Max(MaxPolish, Polish);
AvgPolish += Polish;
Count++;
}
});
if (Count > 0)
{
AvgStrain /= Count;
AvgPolish /= Count;
}
UE_LOG(LogTemp, Warning, TEXT("========== Vertex Attributes Debug =========="));
UE_LOG(LogTemp, Warning, TEXT("Vertices: %d"), Count);
UE_LOG(LogTemp, Warning, TEXT("Strain: Min=%.3f, Max=%.3f, Avg=%.3f"), MinStrain, MaxStrain, AvgStrain);
UE_LOG(LogTemp, Warning, TEXT("Polish: Min=%.3f, Max=%.3f, Avg=%.3f"), MinPolish, MaxPolish, AvgPolish);
UE_LOG(LogTemp, Warning, TEXT("==========================================="));
// 屏幕显示
if (GEngine)
{
GEngine->AddOnScreenDebugMessage(-1, 5.0f, FColor::Yellow,
FString::Printf(TEXT("Strain: Avg=%.2f | Polish: Avg=%.2f"), AvgStrain, AvgPolish));
}
}
Step 3: 编译并测试
Run: 在编辑器中调用调试函数
Expected: 看到流动方向箭头和边缘顶点标记
Commit: git add Source/BladeSmith/DynamicIronBlock.h Source/BladeSmith/DynamicIronBlock.cpp && git commit -m "feat: 添加调试可视化工具"
Task 11: 创建测试场景和验证
Files:
- Create:
Content/Tests/HammerDeformation_Test.umap
Step 1: 创建测试关卡
- 在UE5编辑器中创建新关卡
Content/Tests/HammerDeformation_Test.umap - 添加
ADynamicIronBlock到场景 - 配置不同的参数组合进行测试
Step 2: 测试用例1 - 锻造平面
AnisotropyRatio = 1.0
ImpactRadius = 5.0
BulgeStrength = 0.5
bEnableEdgeConstraint = false
操作: 在铁块顶部中心锤击10次 预期: 材料均匀扩散,表面平整
Step 3: 测试用例2 - 锻造边缘
AnisotropyRatio = 4.0
ImpactRadius = 3.0
BulgeStrength = 0.3
bEnableEdgeConstraint = true
EdgeThreshold = 1.0
操作: 在铁块边缘附近锤击20次 预期: 材料沿边缘方向流动,边缘保持平整
Step 4: 测试用例3 - 打磨度测试
操作: 锤击50次,调用 DebugPrintVertexAttributes
预期: 打磨度逐渐增加,表面颜色从红褐色变为银色
Step 5: 测试用例4 - 网格质量测试
操作: 连续快速锤击100次 预期: 网格保持均匀,无明显退化,60 FPS稳定
Step 6: 记录测试结果
创建文档 docs/tests/hammer-deformation-test-results.md 记录测试结果
Commit: git add Content/Tests/HammerDeformation_Test.umap docs/tests/hammer-deformation-test-results.md && git commit -m "test: 添加锤击形变测试场景和结果"
Task 12: 性能优化和参数调优
Files:
- Modify:
Source/BladeSmith/DynamicIronBlock.h:75-76
Step 1: 添加性能配置参数
// 是否启用自适应优化(仅在锤击间隔较长时执行完整优化)
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Iron Block|Performance")
bool bEnableAdaptiveOptimization = true;
// 最小平滑间隔(秒,低于此值跳过平滑)
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Iron Block|Performance", meta = (ClampMin = "0.0", ClampMax = "1.0"))
float MinSmoothingInterval = 0.1f;
Step 2: 添加时间追踪成员
在头文件 private 部分添加:
float LastHammerImpactTime = 0.0f;
Step 3: 在OnHammerImpact中应用自适应优化
修改Task 9的平滑调用为:
// 记录锤击时间
float CurrentTime = GetWorld()->GetTimeSeconds();
// 自适应优化:仅在锤击间隔足够大时执行完整平滑
bool bShouldSmooth = !bEnableAdaptiveOptimization || (CurrentTime - LastHammerImpactTime > MinSmoothingInterval);
if (bShouldSmooth)
{
// 应用Laplacian平滑(轻微,保持形状细节)
ApplyLaplacianSmoothing(EditMesh, LocalHitLocation, BulgeOuterRadius, 0.1f);
}
LastHammerImpactTime = CurrentTime;
Step 4: 使用Unreal Insights进行性能分析
Run: 启动游戏并启用Unreal Insights Expected: 锤击响应时间<10ms
Step 5: 根据分析结果调优参数
根据实际性能测试结果调整:
AnisotropyRatio默认值BulgeStrength默认值FalloffExponent默认值- 平滑
Alpha值
Commit: git add Source/BladeSmith/DynamicIronBlock.h Source/BladeSmith/DynamicIronBlock.cpp && git commit -m "perf: 添加自适应优化和性能调优"
Task 13: 文档更新
Files:
- Modify:
CLAUDE.md - Create:
docs/features/hammer-deformation-system.md
Step 1: 更新CLAUDE.md
在架构概述部分添加:
### 核心系统(阶段1更新后)
**1. 动态网格形变系统(增强)**
- `ADynamicIronBlock` - 支持各向异性材料流动
- 顶点属性层:初始位置、累积应变、硬度系数、打磨度
- 边缘检测与约束:保持刀刃平整锐利
- 表面磨损可视化:基于打磨度的颜色混合
**关键特性:**
- 各向异性材料流动(AnisotropyRatio控制方向性)
- 锤击时自动更新打磨度(表面从生锈到光亮)
- Laplacian平滑维护网格质量
- 自适应优化策略
Step 2: 创建功能文档
创建 docs/features/hammer-deformation-system.md:
# 锤击形变系统
## 概述
实现高真实度的锻造锤击形变,支持各向异性材料流动、边缘约束、表面磨损和网格质量维护。
## 参数说明
### 材料属性
- **FalloffExponent** (2.0): 控制边缘过渡的平滑度
- **BulgeRatio** (0.8): 凸起区域半径相对于压缩区域的倍数
- **BulgeStrength** (0.5): 凸起高度强度
- **AnisotropyRatio** (3.0): 各向异性比,控制材料流动方向性
- 1.0 = 均匀流动(锻造平面)
- 3.0-5.0 = 方向性流动(锻造边缘)
- 8.0-10.0 = 极端定向(精细整形)
### 边缘约束
- **bEnableEdgeConstraint** (false): 是否启用边缘平整约束
- **EdgeThreshold** (0.5cm): 边缘检测阈值
### 性能
- **bEnableAdaptiveOptimization** (true): 自适应优化开关
- **MinSmoothingInterval** (0.1s): 最小平滑间隔
## 使用方法
### 基础锤击
```cpp
IronBlock->OnHammerImpact(
HitLocation, // 锤击位置(世界坐标)
HitNormal, // 锤击法线
5.0f, // 影响半径(cm)
2.0f, // 形变深度(cm)
1.0f // 锤击力度(0-1)
);
调试工具
// 可视化材料流动方向
IronBlock->DebugDrawFlowDirections();
// 可视化边缘顶点
IronBlock->DebugDrawEdgeVertices();
// 输出顶点属性统计
IronBlock->DebugPrintVertexAttributes();
顶点属性系统
每个顶点存储以下属性:
- InitialPositions: 初始位置,用于无漂移纹理采样
- AccumulatedStrain: 累积塑性应变,记录形变历史
- HardnessCoefficient: 硬度系数(预留,阶段3使用)
- PolishAmount: 打磨度,0=生锈,1=光亮
性能指标
- 目标帧时间: <7ms/次锤击
- 支持顶点数: 20-50万
- 稳定帧率: 60 FPS @ 连续锤击
验收标准
- 各向异性材料流动正常工作
- 锤击后表面颜色变化(铁锈→金属)
- 边缘约束保持刀刃平整
- 连续锤击100次网格质量良好
- 性能达标,60 FPS稳定
**Step 3: 提交文档**
Commit: `git add CLAUDE.md docs/features/hammer-deformation-system.md && git commit -m "docs: 更新锤击形变系统文档"`
---
## 验收标准总结
完成所有任务后,系统应满足:
1. **功能完整性**
- [ ] 各向异性材料流动工作正常
- [ ] 顶点属性层正确初始化和更新
- [ ] 打磨度在锤击时正确增加
- [ ] 表面颜色从红褐色变为银色
- [ ] 边缘约束保持刀刃平整
- [ ] Laplacian平滑维护网格质量
2. **性能要求**
- [ ] 单次锤击响应时间 <10ms
- [ ] 连续锤击60 FPS稳定
- [ ] 20万顶点网格性能良好
3. **可调试性**
- [ ] 流动方向可视化工具
- [ ] 边缘顶点可视化工具
- [ ] 顶点属性统计输出
- [ ] 所有参数可在编辑器调节
4. **代码质量**
- [ ] 遵循项目编码规范
- [ ] 每个Task独立提交
- [ ] 提交信息清晰
- [ ] 文档完整
---
**文档版本**: 1.0
**创建日期**: 2026-01-23
**预计工期**: 阶段1核心功能 (2-3天)
**状态**: 计划完成,待实施