56cb314799
原 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>
1270 lines
38 KiB
Markdown
1270 lines
38 KiB
Markdown
# 锤击形变系统增强实施计划
|
||
|
||
> **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天)
|
||
**状态**: 计划完成,待实施
|