# 锤击形变系统增强实施计划 > **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>> InitialPositions; // 初始位置(不变,用于无漂移纹理采样) TSharedPtr>> AccumulatedStrain; // 累积塑性应变 TSharedPtr>> HardnessCoefficient; // 硬度系数(影响形变阻力) TSharedPtr>> 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>>(EditMesh); InitialPositions->Initialize("InitialPosition"); // 创建累积应变属性 AccumulatedStrain = MakeShared>>(EditMesh); AccumulatedStrain->Initialize("AccumulatedStrain"); // 创建硬度系数属性 HardnessCoefficient = MakeShared>>(EditMesh); HardnessCoefficient->Initialize("HardnessCoefficient"); // 创建打磨度属性 PolishAmount = MakeShared>>(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& OutEdgeVertices) const; /** * 对边缘顶点应用线性约束 */ void ApplyEdgeLinearConstraint(FDynamicMesh3& Mesh, const TSet& EdgeVertices); ``` **Step 2: 实现边缘检测** 在 `Source/BladeSmith/DynamicIronBlock.cpp:1450` 之后添加: ```cpp void ADynamicIronBlock::MarkEdgeVertices(const FDynamicMesh3& Mesh, TSet& 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& EdgeVertices) { // 简化版本:仅平滑边缘顶点,不进行完整的PCA拟合 for (int32 VID : EdgeVertices) { if (!Mesh.IsVertex(VID)) continue; // 找到此顶点的边缘邻居 TArray 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 EdgeVertices; MarkEdgeVertices(EditMesh, EdgeVertices); // 只处理受影响区域的边缘顶点 TSet 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 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>(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 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& 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 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天) **状态**: 计划完成,待实施