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>
19 KiB
19 KiB
刀刃形成技术细节
所属系统: 高真实度锻造系统 文档类型: 技术实现细节 最后更新: 2026-01-22
1. 问题分析
1.1 核心难点
锻造刀刃时需要:
- 材料沿边缘方向流动(而非均匀扩散)
- 边缘保持平整锐利(而非波浪状凸起)
- 厚度逐渐减小(形成薄片状结构)
当前系统的球形凸起(BulgeRatio)只能处理块状形变,无法满足刀刃形成的方向性需求。
1.2 真实锻造物理过程
初始状态:圆柱形铁块
███████████
███████████
第一次锻打:
▼锤击
███████████ ← 受击处变扁
█████████████ ← 材料向侧面流动
多次锻打:
██ ← 继续变薄
███████
███████████ ← 逐渐形成锥形
最终成刃:
▲ ← 边缘平整锐利
█████
████████
关键:材料要优先沿着边缘方向流动(垂直于锤击方向 + 朝向边缘)。
2. 技术方案:各向异性材料流动
2.1 核心思想
将凸起从球形改为椭球形,长轴沿着"最小阻力方向"(边缘方向)。
2.2 方向检测算法
/**
* 查找最近的自由边界方向
* 材料会优先朝向自由边界流动(阻力最小)
*/
FVector FindNearestFreeEdgeDirection(
const FDynamicMesh3& Mesh,
const FVector& LocalPos,
const FVector& BoxHalfSize)
{
// 计算到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];
}
/**
* 计算材料流动的主方向
* 主方向 = 朝向边缘 + 垂直于锤击方向
*/
FVector3f CalculatePrimaryFlowDirection(
const FVector& HammerDir, // 锤击方向(通常是-Z)
const FVector& ToNearestEdge) // 朝向最近边缘的方向
{
// 移除锤击方向的分量(材料不能向锤头方向流动)
FVector3f PrimaryFlow = FVector3f(ToNearestEdge) -
FVector3f(HammerDir) * FVector::DotProduct(ToNearestEdge, HammerDir);
return PrimaryFlow.GetSafeNormal();
}
/**
* 计算次要流动方向(垂直于主方向和锤击方向)
*/
FVector3f CalculateSecondaryFlowDirection(
const FVector& HammerDir,
const FVector& PrimaryFlow)
{
return FVector3f::CrossProduct(FVector3f(HammerDir), PrimaryFlow).GetSafeNormal();
}
2.3 各向异性凸起计算
/**
* 应用各向异性材料流动
* @param LocalHitPos 锤击位置(局部空间)
* @param ImpactRadius 锤击影响半径
* @param HammerDir 锤击方向
* @param AnisotropyRatio 各向异性比(主方向/次方向的流动强度比)
*/
void ApplyAnisotropicDeformation(
FDynamicMesh3& Mesh,
const FVector& LocalHitPos,
float ImpactRadius,
const FVector& HammerDir,
float AnisotropyRatio, // 默认3.0,主方向流动强度是次方向的3倍
float BulgeStrength)
{
// 计算流动方向
FVector ToNearestEdge = FindNearestFreeEdgeDirection(Mesh, LocalHitPos, BoxHalfSize);
FVector3f PrimaryFlow = CalculatePrimaryFlowDirection(HammerDir, ToNearestEdge);
FVector3f SecondaryFlow = CalculateSecondaryFlowDirection(HammerDir, PrimaryFlow);
// 对每个顶点应用形变
for (int32 VID : Mesh.VertexIndicesItr())
{
FVector VertexPos = Mesh.GetVertex(VID);
float Distance = FVector::Dist(VertexPos, LocalHitPos);
if (Distance < ImpactRadius)
{
// 将位置投影到流动方向
float DistAlongPrimary = FVector::DotProduct(
FVector(VertexPos - LocalHitPos), PrimaryFlow);
float DistAlongSecondary = FVector::DotProduct(
FVector(VertexPos - LocalHitPos), SecondaryFlow);
// 各向异性距离计算
// 主方向的"有效距离"更小(更容易到达)→ 凸起更大
float EffectiveDistance = FMath::Sqrt(
FMath::Square(DistAlongPrimary / AnisotropyRatio) +
FMath::Square(DistAlongSecondary)
);
float DistanceRatio = EffectiveDistance / ImpactRadius;
if (DistanceRatio < 1.0f)
{
// 使用FalloffExponent控制边缘过渡
float FalloffExponent = 2.0f;
float BulgeFactor = BulgeStrength *
FMath::Pow(1.0f - DistanceRatio, FalloffExponent);
// 凸起方向 = 锤击方向(材料被挤压向外)
FVector Displacement = HammerDir * (-BulgeFactor);
// 应用形变
Mesh.SetVertex(VID, VertexPos + Displacement);
}
}
}
}
2.4 参数说明
| 参数 | 默认值 | 范围 | 效果 |
|---|---|---|---|
| AnisotropyRatio | 3.0 | 1.0-10.0 | 主方向流动强度比。越大,边缘拉伸越明显 |
| BulgeStrength | 0.5 | 0.1-2.0 | 凸起高度。需要配合ImpactRadius调整 |
| ImpactRadius | 5.0 | 1.0-20.0 | 锤击影响半径(cm) |
| FalloffExponent | 2.0 | 1.0-4.0 | 边缘过渡平滑度。越大,边界越锐利 |
调参建议:
- 锻造平面: AnisotropyRatio = 1.0(均匀流动)
- 锻造边缘: AnisotropyRatio = 3.0-5.0(明显方向性)
- 精细整形: AnisotropyRatio = 8.0-10.0(极端定向)
3. 边缘平整约束(可选增强)
3.1 问题
即使有各向异性流动,边缘仍可能有轻微波纹:
理想边缘: ▲
████
██████
实际边缘: ▲▲▲
████
██████ ← 波纹状
3.2 解决方案:边缘顶点几何约束
/**
* 检测并标记边缘顶点
* 距离任何边界< EdgeThreshold 的顶点
*/
void MarkEdgeVertices(
FDynamicMesh3& Mesh,
TSet<int32>& OutEdgeVertices,
float EdgeThreshold = 0.5f) // 5mm
{
FVector BoxHalfSize = GetBoxHalfSize();
for (int32 VID : Mesh.VertexIndicesItr())
{
FVector Pos = Mesh.GetVertex(VID);
// 检查是否接近任何边界
bool bNearEdge = false;
bNearEdge |= FMath::Abs(Pos.X - BoxHalfSize.X) < EdgeThreshold;
bNearEdge |= FMath::Abs(Pos.X + BoxHalfSize.X) < EdgeThreshold;
bNearEdge |= FMath::Abs(Pos.Y - BoxHalfSize.Y) < EdgeThreshold;
bNearEdge |= FMath::Abs(Pos.Y + BoxHalfSize.Y) < EdgeThreshold;
bNearEdge |= FMath::Abs(Pos.Z - BoxHalfSize.Z) < EdgeThreshold;
bNearEdge |= FMath::Abs(Pos.Z + BoxHalfSize.Z) < EdgeThreshold;
if (bNearEdge)
{
OutEdgeVertices.Add(VID);
}
}
}
/**
* 对边缘顶点应用线性约束
* 形变后将边缘顶点投影到拟合直线上,保持平整
*/
void ApplyEdgeLinearConstraint(
FDynamicMesh3& Mesh,
const TSet<int32>& EdgeVertices)
{
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)
{
// 使用主成分分析(PCA)拟合直线
FVector CurrentPos = Mesh.GetVertex(VID);
FVector LineOrigin, LineDirection;
FitLineToPoints(Mesh, EdgeNeighbors, LineOrigin, LineDirection);
// 投影到拟合线上
FVector ProjectedPos = ProjectPointToLine(CurrentPos, LineOrigin, LineDirection);
// 稍微平滑更新(避免突变)
FVector SmoothPos = FMath::Lerp(CurrentPos, ProjectedPos, 0.5f);
Mesh.SetVertex(VID, SmoothPos);
}
}
}
/**
* 使用PCA拟合直线到点集
*/
void FitLineToPoints(
const FDynamicMesh3& Mesh,
const TArray<int32>& VertexIDs,
FVector& OutOrigin,
FVector& OutDirection)
{
// 计算质心
FVector Centroid = FVector::ZeroVector;
for (int32 VID : VertexIDs)
{
Centroid += Mesh.GetVertex(VID);
}
Centroid /= VertexIDs.Num();
// 计算协方差矩阵
float CovXX = 0, CovYY = 0, CovZZ = 0;
float CovXY = 0, CovXZ = 0, CovYZ = 0;
for (int32 VID : VertexIDs)
{
FVector P = Mesh.GetVertex(VID) - Centroid;
CovXX += P.X * P.X;
CovYY += P.Y * P.Y;
CovZZ += P.Z * P.Z;
CovXY += P.X * P.Y;
CovXZ += P.X * P.Z;
CovYZ += P.Y * P.Z;
}
// 构建协方差矩阵
FMatrix33 CovMatrix(
FVector3f(CovXX, CovXY, CovXZ),
FVector3f(CovXY, CovYY, CovYZ),
FVector3f(CovXZ, CovYZ, CovZZ)
);
// 计算特征向量(主方向 = 最大特征值对应的向量)
FVector3f EigenValues;
FMatrix33 EigenVectors;
FMatrix33::EigenDecomposition(CovMatrix, EigenValues, EigenVectors);
// 最大特征值对应的列向量是主方向
OutOrigin = Centroid;
OutDirection = FVector(EigenVectors.GetColumn(0)); // 第一主成分
}
/**
* 将点投影到直线上
*/
FVector ProjectPointToLine(
const FVector& Point,
const FVector& LineOrigin,
const FVector& LineDirection)
{
FVector ToPoint = Point - LineOrigin;
float ProjectionLength = FVector::DotProduct(ToPoint, LineDirection);
return LineOrigin + LineDirection * ProjectionLength;
}
3.3 完整锤击流程
void ADynamicIronBlock::OnHammerImpact(
FVector HitLocation,
FVector HitNormal,
float ImpactRadius,
float ImpactDepth,
float ImpactForce)
{
FVector LocalHitPos = GetTransform().InverseTransformPosition(HitLocation);
FVector HammerDir = GetTransform().InverseTransformPosition(HitNormal);
DynamicMesh->EditMesh([&](FDynamicMesh3& Mesh)
{
// 1. 应用各向异性形变
ApplyAnisotropicDeformation(
Mesh,
LocalHitPos,
ImpactRadius,
HammerDir,
AnisotropyRatio,
BulgeStrength
);
// 2. (可选)边缘平整约束
if (bEnableEdgeConstraint)
{
TSet<int32> EdgeVertices;
MarkEdgeVertices(Mesh, EdgeVertices, EdgeThreshold);
// 只处理受影响区域的边缘顶点
TSet<int32> AffectedEdges;
for (int32 VID : EdgeVertices)
{
FVector Pos = Mesh.GetVertex(VID);
if (FVector::Dist(Pos, LocalHitPos) < ImpactRadius * 1.5f)
{
AffectedEdges.Add(VID);
}
}
ApplyEdgeLinearConstraint(Mesh, AffectedEdges);
}
});
}
4. 自适应网格细分(可选高级功能)
4.1 目的
刀刃区域需要更高密度才能表现锐利边缘:
低密度(5万顶点):
▲
████ ← 锯齿状,不平整
高密度(200万顶点):
▲
████ ← 平滑锐利
██████
4.2 曲率检测与细分
/**
* 检测高曲率边缘并细分
*/
void SubdivideHighCurvatureEdges(
FDynamicMesh3& Mesh,
float CurvatureThreshold, // 曲率阈值
float MaxEdgeLength, // 最大边长
int32 MaxSubdivisions) // 最大细分次数
{
int32 SubdivisionCount = 0;
for (int32 EID : Mesh.EdgeIndicesItr())
{
if (SubdivisionCount >= MaxSubdivisions)
break;
// 跳过边界边
if (Mesh.IsBoundaryEdge(EID))
continue;
FIndex2i EdgeVerts = Mesh.GetEdgeV(EID);
FVector V0 = Mesh.GetVertex(EdgeVerts.A);
FVector V1 = Mesh.GetVertex(EdgeVerts.B);
float EdgeLength = FVector::Dist(V0, V1);
// 边太长或曲率太高 → 细分
float EdgeCurvature = CalculateEdgeCurvature(Mesh, EID);
if (EdgeLength > MaxEdgeLength && EdgeCurvature > CurvatureThreshold)
{
// 细分此边
FDynamicMesh3::FEdgeSplitInfo SplitInfo;
EMeshResult Result = Mesh.SplitEdge(EID, SplitInfo);
if (Result == EMeshResult::Ok)
{
// 新顶点位置 = 中点
FVector MidPoint = (V0 + V1) * 0.5f;
Mesh.SetVertex(SplitInfo.NewVertex, MidPoint);
// 插值顶点属性
InterpolateVertexAttributes(Mesh, EdgeVerts.A, EdgeVerts.B, SplitInfo.NewVertex);
SubdivisionCount++;
}
}
}
}
/**
* 计算边的曲率(基于邻面法线夹角)
*/
float CalculateEdgeCurvature(const FDynamicMesh3& Mesh, int32 EdgeID)
{
FIndex2i EdgeTris = Mesh.GetEdgeT(EdgeID);
// 边界边曲率为0
if (EdgeTris.B == IndexConstants::InvalidID)
return 0.0f;
// 获取两个三角形的法线
FVector Normal0 = Mesh.GetTriNormal(EdgeTris.A);
FVector Normal1 = Mesh.GetTriNormal(EdgeTris.B);
// 法线夹角 = 曲率度量
float Dot = FVector::DotProduct(Normal0, Normal1);
return FMath::Acos(Dot); // 返回弧度
}
/**
* 插值顶点属性
* 确保新顶点继承正确的材料状态
*/
void InterpolateVertexAttributes(
FDynamicMesh3& Mesh,
int32 VID0,
int32 VID1,
int32 NewVID)
{
// 位置(已由SplitEdge处理)
// 初始位置
FVector InitPos0 = InitialPositions->GetValue(VID0);
FVector InitPos1 = InitialPositions->GetValue(VID1);
InitialPositions->SetValue(NewVID, (InitPos0 + InitPos1) * 0.5f);
// 打磨度
float Polish0 = PolishAmount->GetValue(VID0);
float Polish1 = PolishAmount->GetValue(VID1);
PolishAmount->SetValue(NewVID, (Polish0 + Polish1) * 0.5f);
// 累积应变
float Strain0 = AccumulatedStrain->GetValue(VID0);
float Strain1 = AccumulatedStrain->GetValue(VID1);
AccumulatedStrain->SetValue(NewVID, (Strain0 + Strain1) * 0.5f);
// 硬度
float Hardness0 = HardnessCoefficient->GetValue(VID0);
float Hardness1 = HardnessCoefficient->GetValue(VID1);
HardnessCoefficient->SetValue(NewVID, (Hardness0 + Hardness1) * 0.5f);
// 晶粒尺寸
float Grain0 = GrainSize->GetValue(VID0);
float Grain1 = GrainSize->GetValue(VID1);
GrainSize->SetValue(NewVID, (Grain0 + Grain1) * 0.5f);
// 顶点颜色
FColor Color0 = Mesh.GetVertexColor(VID0);
FColor Color1 = Mesh.GetVertexColor(VID1);
FLinearColor LinearColor0(Color0);
FLinearColor LinearColor1(Color1);
FLinearColor AvgColor = (LinearColor0 + LinearColor1) * 0.5f;
Mesh.SetVertexColor(NewVID, AvgColor.ToFColor());
}
4.3 集成到锤击流程
void ADynamicIronBlock::OnHammerImpact(...)
{
// ... 形变计算 ...
// 自适应细分(每隔几次锤击执行,避免过度细分)
if (bEnableAdaptiveRefinement && HammerHitCount % 5 == 0)
{
SubdivideHighCurvatureEdges(
*DynamicMesh,
CurvatureThreshold = 0.5f, // ~30度角
MaxEdgeLength = 2.0f, // 2cm
MaxSubdivisions = 100
);
}
}
5. 性能考虑
5.1 计算复杂度
| 操作 | 复杂度 | 实际耗时(20万顶点) |
|---|---|---|
| 各向异性凸起 | O(N) | 2-3ms |
| 边缘约束(PCA) | O(N×K) | +3-5ms |
| 自适应细分 | O(E) | +2-4ms |
| 属性插值 | O(1) per顶点 | 可忽略 |
5.2 优化建议
- 限制约束区域:只对受影响的边缘顶点应用约束
- 降低细分频率:每5-10次锤击才细分一次
- LOD策略:远距离铁块使用简化约束
- 异步计算:细分可以延后到帧尾执行
6. 调试工具
6.1 可视化流动方向
void ADynamicIronBlock::DebugDrawFlowDirections()
{
FVector BoxHalfSize = GetBoxHalfSize();
// 在网格表面采样点
for (int32 VID : DynamicMesh->VertexIndicesItr())
{
if (VID % 100 != 0) // 稀疏采样
continue;
FVector Pos = GetActorTransform().TransformPosition(DynamicMesh->GetVertex(VID));
// 计算流动方向
FVector ToNearestEdge = FindNearestFreeEdgeDirection(*DynamicMesh,
DynamicMesh->GetVertex(VID), BoxHalfSize);
FVector PrimaryFlow = CalculatePrimaryFlowDirection(FVector::DownVector, ToNearestEdge);
// 绘制方向箭头
DrawDebugDirectionalArrow(
GetWorld(),
Pos,
Pos + GetActorTransform().TransformVector(PrimaryFlow) * 5.0f,
2.0f,
FColor::Cyan,
false,
5.0f,
0,
0.5f
);
}
}
6.2 可视化边缘顶点
void ADynamicIronBlock::DebugDrawEdgeVertices()
{
TSet<int32> EdgeVertices;
MarkEdgeVertices(*DynamicMesh, EdgeVertices, EdgeThreshold);
for (int32 VID : EdgeVertices)
{
FVector Pos = GetActorTransform().TransformPosition(DynamicMesh->GetVertex(VID));
DrawDebugPoint(GetWorld(), Pos, 8.0f, FColor::Red, false, 5.0f);
}
}
7. 参数配置示例
7.1 锻造平面(打铁块顶面)
AnisotropyRatio = 1.0f; // 均匀流动
ImpactRadius = 5.0f;
BulgeStrength = 0.5f;
bEnableEdgeConstraint = false;
7.2 锻造边缘(形成刀刃)
AnisotropyRatio = 4.0f; // 强方向性
ImpactRadius = 3.0f; // 小范围
BulgeStrength = 0.3f; // 较小凸起
bEnableEdgeConstraint = true;
EdgeThreshold = 1.0f; // 1cm
7.3 精细整形(最后修整)
AnisotropyRatio = 8.0f; // 极端定向
ImpactRadius = 1.5f; // 很小范围
BulgeStrength = 0.1f; // 微调
bEnableEdgeConstraint = true;
EdgeThreshold = 0.5f; // 5mm
8. 已知限制
- 极端形变: 当边缘厚度<1mm时,各向异性假设可能失效
- 拓扑变化: 当前方法无法处理网格撕裂或融合(如锻打折叠)
- 多重边缘: 复杂形状(如叉状)的方向检测可能不准确
未来改进方向:
- 基于曲率张量的精确流动方向
- 支持拓扑变化(网格切割与连接)
- 多工具系统(不同锤头形状)