构建节点间距离相等的路径的最佳方法是什么?

时间:2017-08-10 08:33:24

标签: algorithm unity3d

我在2D空间中有一条路径。但节点之间的距离不相等。 我正在寻找一种算法,将节点添加到源路径,以便节点之间的距离相等。 什么是最佳做法?

示例图片:

2 个答案:

答案 0 :(得分:5)

几何上不可能为多个任意路径段生成等距点 - 只有当它们的长度共享一个公约数时才有可能。

但是,您可以使用以下方法生成最接近的匹配点集:

  1. 您需要先在路径段上设置所需的最大点数N。这是为了阻止算法无限循环 - 因为在一般情况下,只有无数个分区才能给出确切的答案,而这不是我们想要的。
    • 但是,在应用此功能之前,我们需要检查N + 1是否不小于最短路径比率。如果是,那么我们需要调整它。
  2. 对于每个路径段迭代到最大点数N,计算每个数字的除法长度L。对于每个迭代值,我们将Cost变量定义为计算解决方案与理想之间总差异的之和。
  3. 然后遍历每个其他路径段。将其长度M除以L,得出比率R
    • 如果R是一个整数,那么对于这个段,我们找到了一个精确的解决方案。将零添加到Cost
    • 否则请A = floor(R), B = ceil(R)。计算两个单独的费用cost_A = abs(M - L * A),同样地计算B
    • 如果cost_A < cost_B,请将C = A作为此细分的最佳分组计数,反之亦然。记录C
    • 选择min(cost_A, cost_B)并添加到Cost。继续。
  4. 请记住跟踪每个路径段C的最佳值列表,以及记录当前计算的“工作列表”。还要跟踪min_Cost变量。
    • 如果在每个细分Cost < min_Cost的主循环结束时,请更新min_Cost和最佳列表。
  5. 以上描述可能看起来有点模糊。这里有一些C#代码 - 道歉,因为我不熟悉C#Mono / Unity的细节,所以你可能不得不在这里或那里替换一些类型名称/函数名称,但算法的要点是希望你想要的

    public static int[] calculateOptimalSplitNumbers(Point[] path, int N)
    {
       int no_segs = path.Length - 1;
       if (no_segs <= 1) return null;
    
       double[] lengths = new double[no_segs];
       for (int i = 0; i < no_segs; i++)
          lengths[i] = Vector.LengthOf(path[i + 1] - path[i]); // replace with the correct unity function?
    
       int max_ratio = Math.Floor(Math.Max(lengths) / Math.Min(lengths)) - 1;
       if (N < max_ratio)
          N = max_ratio;
    
       double min_Cost = double.MaxValue;
       int[] min_List = new int[no_segs];
    
       int[] cur_List = new int[no_segs];
       for (int i = 0; i < no_segs; i++)
       {
          double cost = 0.0;
          for (int j = 0; j < N; j++)
          {
             double L = lengths[i] / (j + 2);
             cur_list[i] = j + 1;
    
             for (int k = 0; k < no_segs; k++)
             {
                if (k == i) continue;
    
                double M = lengths[k],
                       R = M / L;
                // path is too short - put no points
                if (R < 1.0) {
                   cur_list[k] = 0;
                   cost += M - L;
                }
    
                int A = Math.Floor(R), 
                    B = Math.Ceiling(R);
                double cost_A = Math.Abs(M - L * A),
                       cost_B = Math.Abs(M - L * B);
                if (cost_A < cost_B) {
                   cur_list[k] = A;
                   cost += cost_A;
                }
                else {
                   cur_list[k] = B;
                   cost += cost_B;
                }
             }
          }
    
          if (cost < min_Cost) {
             min_Cost = cost;
             System.Array.Copy(cur_List, min_List, no_segs);
          }
       }
    
       return min_List;
    }
    

    代码采用路径点数组并返回要放在每个路径段上的点数。如果您需要对代码的任何更多解释,请告诉我,我将编辑更多的评论。

答案 1 :(得分:1)

我决定共享路径规范化工具(根据@meowgoesthedog方法)在Unity3D中使用它:

using System; using System.Collections.Generic; using UnityEngine;

/// <summary>
/// Represents helper that normalizes the path in a way 
/// that distance between all nodes become almost equal.
/// </summary>
public static class PathNormalizer
{
    /// <summary>
    /// Normalizes the specified vector path.
    /// </summary>
    /// <param name="vectorPath">The vector path.</param>
    /// <param name="minSplitsBySegment">The minimum splits by segment.</param>
    public static Vector3[] Normalize(Vector3[] vectorPath, int minSplitsBySegment)
    {
        if (vectorPath.Length < 3)
        {
            return vectorPath;
        }

        var segmentsSplits = CalculateOptimalSplitNumbers(vectorPath, minSplitsBySegment);
        if (segmentsSplits == null)
        {
            Debug.LogWarning("Can't normalize path");
            return vectorPath;
        }

        List<Vector3> newPath = new List<Vector3>();

        for (int i = 1; i < vectorPath.Length; i++)
        {
            var split = segmentsSplits[i - 1];
            for (int j = 0; j < split; j++)
            {
                var newNode = Vector3.Lerp(vectorPath[i - 1], vectorPath[i], (float)j / split);
                newPath.Add(newNode);
            }
        }

        newPath.Add(vectorPath[vectorPath.Length - 1]);
        return newPath.ToArray();
    }

    private static int[] CalculateOptimalSplitNumbers(Vector3[] path, int minSplitsBySegment)
    {
        int noSegs = path.Length - 1;
        if (noSegs <= 1) return null;

        float[] lengths = new float[noSegs];
        for (int i = 0; i < noSegs; i++)
            lengths[i] = Vector3.Distance(path[i + 1], path[i]);

        float minLenght = float.MaxValue;
        float maxLenght = 0;

        foreach (var length in lengths)
        {
            if (length < minLenght)
            {
                minLenght = length;
            }

            if (length > maxLenght)
            {
                maxLenght = length;
            }
        }

        int maxRatio = (int)Math.Floor(maxLenght / minLenght) - 1;
        if (minSplitsBySegment < maxRatio)
            minSplitsBySegment = maxRatio;

        double minCost = double.MaxValue;
        int[] minList = new int[noSegs];

        int[] curList = new int[noSegs];

        for (int i = 0; i < noSegs; i++)
        {
            double cost = 0.0;
            for (int j = 0; j < minSplitsBySegment; j++)
            {
                double l = lengths[i] / (j + 2);
                curList[i] = j + 1;

                for (int k = 0; k < noSegs; k++)
                {
                    if (k == i) continue;

                    double m = lengths[k],
                        r = m / l;

                    // path is too short - put no points
                    if (r < 1.0)
                    {
                        curList[k] = 0;
                        cost += m - l;
                    }

                    int a = (int)Math.Floor(r),
                        b = (int)Math.Ceiling(r);
                    double costA = Math.Abs(m - l * a),
                        costB = Math.Abs(m - l * b);
                    if (costA < costB)
                    {
                        curList[k] = a;
                        cost += costA;
                    }
                    else
                    {
                        curList[k] = b;
                        cost += costB;
                    }
                }
            }

            if (cost < minCost)
            {
                minCost = cost;
                Array.Copy(curList, minList, noSegs);
            }
        }

        return minList;
    }
}