我在2D空间中有一条路径。但节点之间的距离不相等。 我正在寻找一种算法,将节点添加到源路径,以便节点之间的距离相等。 什么是最佳做法?
示例图片:
答案 0 :(得分:5)
几何上不可能为多个任意路径段生成等距点 - 只有当它们的长度共享一个公约数时才有可能。
但是,您可以使用以下方法生成最接近的匹配点集:
N
。这是为了阻止算法无限循环 - 因为在一般情况下,只有无数个分区才能给出确切的答案,而这不是我们想要的。
N + 1
是否不小于最短路径的比率。如果是,那么我们需要调整它。N
,计算每个数字的除法长度L
。对于每个迭代值,我们将Cost
变量定义为计算解决方案与理想之间总差异的之和。 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
。继续。C
的最佳值列表,以及记录当前计算的“工作列表”。还要跟踪min_Cost
变量。
Cost < min_Cost
的主循环结束时,请更新min_Cost
和最佳列表。以上描述可能看起来有点模糊。这里有一些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;
}
}