Files
BladeSmith/docs/plans/2026-01-22-刀刃形成技术细节.md
T
2026-01-23 00:31:21 +08:00

19 KiB
Raw Blame History

刀刃形成技术细节

所属系统: 高真实度锻造系统 文档类型: 技术实现细节 最后更新: 2026-01-22


1. 问题分析

1.1 核心难点

锻造刀刃时需要:

  1. 材料沿边缘方向流动(而非均匀扩散)
  2. 边缘保持平整锐利(而非波浪状凸起)
  3. 厚度逐渐减小(形成薄片状结构)

当前系统的球形凸起(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 优化建议

  1. 限制约束区域:只对受影响的边缘顶点应用约束
  2. 降低细分频率:每5-10次锤击才细分一次
  3. LOD策略:远距离铁块使用简化约束
  4. 异步计算:细分可以延后到帧尾执行

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. 已知限制

  1. 极端形变: 当边缘厚度<1mm时,各向异性假设可能失效
  2. 拓扑变化: 当前方法无法处理网格撕裂或融合(如锻打折叠)
  3. 多重边缘: 复杂形状(如叉状)的方向检测可能不准确

未来改进方向:

  • 基于曲率张量的精确流动方向
  • 支持拓扑变化(网格切割与连接)
  • 多工具系统(不同锤头形状)