Files
BladeSmith/docs/impl/2026-01-23-hammer-deformation-enhancement.md
T
starmen1265 56cb314799 docs: 归档原 plans 目录,迁移实施清单到 impl/
原 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>
2026-08-11 08:57:46 +08:00

1270 lines
38 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 锤击形变系统增强实施计划
> **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` 部分添加:
```cpp
// ========== 顶点属性层(阶段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` 部分添加函数声明:
```cpp
/**
* 初始化顶点属性层
*/
void InitializeVertexAttributes();
/**
* 释放顶点属性层
*/
void ReleaseVertexAttributes();
```
**Step 3: 在 OnConstruction 中调用初始化**
在 `Source/BladeSmith/DynamicIronBlock.cpp:236` 之后添加:
```cpp
// 初始化顶点属性层
InitializeVertexAttributes();
```
**Step 4: 在 EndPlay 中调用释放**
在 `Source/BladeSmith/DynamicIronBlock.cpp:119` 之前添加:
```cpp
// 释放顶点属性层
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: 实现初始化函数**
在文件末尾添加:
```cpp
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: 实现释放函数**
在文件末尾添加:
```cpp
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: 添加可编辑参数**
在材质属性区域添加:
```cpp
// 各向异性比(主方向/次方向流动强度比,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` 部分添加:
```cpp
/**
* 查找最近的自由边界方向
* 材料会优先朝向自由边界流动(阻力最小)
*/
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` 之后添加:
```cpp
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主流程**
```cpp
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` 部分添加:
```cpp
/**
* 检测并标记边缘顶点
*/
void MarkEdgeVertices(const FDynamicMesh3& Mesh, TSet<int32>& OutEdgeVertices) const;
/**
* 对边缘顶点应用线性约束
*/
void ApplyEdgeLinearConstraint(FDynamicMesh3& Mesh, const TSet<int32>& EdgeVertices);
```
**Step 2: 实现边缘检测**
在 `Source/BladeSmith/DynamicIronBlock.cpp:1450` 之后添加:
```cpp
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` 之前添加:
```cpp
// 边缘约束(如果启用)
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中应用形变的位置后添加:
```cpp
// 更新打磨度(锤击使表面变亮)
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` 函数,修改颜色计算部分:
```cpp
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: 添加函数声明**
```cpp
/**
* 应用Laplacian平滑到受影响区域
*/
void ApplyLaplacianSmoothing(FDynamicMesh3& Mesh, const FVector& Center, float Radius, float Alpha = 0.3f);
```
**Step 2: 实现平滑函数**
在 `Source/BladeSmith/DynamicIronBlock.cpp:1500` 之后添加:
```cpp
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的边缘约束之后添加:
```cpp
// 应用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: 添加调试函数声明**
```cpp
/**
* 调试函数 - 可视化材料流动方向
*/
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` 之后添加:
```cpp
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: 创建测试关卡**
1. 在UE5编辑器中创建新关卡 `Content/Tests/HammerDeformation_Test.umap`
2. 添加 `ADynamicIronBlock` 到场景
3. 配置不同的参数组合进行测试
**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: 添加性能配置参数**
```cpp
// 是否启用自适应优化(仅在锤击间隔较长时执行完整优化)
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` 部分添加:
```cpp
float LastHammerImpactTime = 0.0f;
```
**Step 3: 在OnHammerImpact中应用自适应优化**
修改Task 9的平滑调用为:
```cpp
// 记录锤击时间
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**
在架构概述部分添加:
```markdown
### 核心系统(阶段1更新后)
**1. 动态网格形变系统(增强)**
- `ADynamicIronBlock` - 支持各向异性材料流动
- 顶点属性层:初始位置、累积应变、硬度系数、打磨度
- 边缘检测与约束:保持刀刃平整锐利
- 表面磨损可视化:基于打磨度的颜色混合
**关键特性:**
- 各向异性材料流动(AnisotropyRatio控制方向性)
- 锤击时自动更新打磨度(表面从生锈到光亮)
- Laplacian平滑维护网格质量
- 自适应优化策略
```
**Step 2: 创建功能文档**
创建 `docs/features/hammer-deformation-system.md`:
```markdown
# 锤击形变系统
## 概述
实现高真实度的锻造锤击形变,支持各向异性材料流动、边缘约束、表面磨损和网格质量维护。
## 参数说明
### 材料属性
- **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)
);
```
### 调试工具
```cpp
// 可视化材料流动方向
IronBlock->DebugDrawFlowDirections();
// 可视化边缘顶点
IronBlock->DebugDrawEdgeVertices();
// 输出顶点属性统计
IronBlock->DebugPrintVertexAttributes();
```
## 顶点属性系统
每个顶点存储以下属性:
- **InitialPositions**: 初始位置,用于无漂移纹理采样
- **AccumulatedStrain**: 累积塑性应变,记录形变历史
- **HardnessCoefficient**: 硬度系数(预留,阶段3使用)
- **PolishAmount**: 打磨度,0=生锈,1=光亮
## 性能指标
- 目标帧时间: <7ms/次锤击
- 支持顶点数: 20-50万
- 稳定帧率: 60 FPS @ 连续锤击
## 验收标准
- [x] 各向异性材料流动正常工作
- [x] 锤击后表面颜色变化(铁锈→金属)
- [x] 边缘约束保持刀刃平整
- [x] 连续锤击100次网格质量良好
- [x] 性能达标,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天)
**状态**: 计划完成,待实施