三次贝塞尔曲线上的恒速
Constant Speed Over Cubic Bezier Curve
我要解决的问题是我似乎无法以恒定速度沿着三次贝塞尔曲线移动二维点。
我遵循了本教程:http://catlikecoding.com/unity/tutorials/curves-and-splines/ 最初实现了曲线,效果非常好。但是当试图以恒定速度逼近该点时,它的方式,方式。
根据我目前所读的内容,您应该遍历曲线,计算每个步长的弧长和间隔距离。然后,将这些距离与目标距离(弧长 * 时间)进行比较以找到最近的距离。如果分辨率足够高,这应该几乎没有误差并且足够准确以满足我的需要。
这是我目前的代码:
积分计算:
public static Vector3 GetPoint (Vector3 p0, Vector3 p1, Vector3 p2, Vector3 p3, float t)
{
t = Mathf.Clamp01(t);
float oneMinusT = 1f - t;
return
oneMinusT * oneMinusT * oneMinusT * p0 +
3f * oneMinusT * oneMinusT * t * p1 +
3f * oneMinusT * t * t * p2 +
t * t * t * p3;
}
在恒定时间计算点的遗憾尝试:
private float GetApproximatedTime(float u)
{
int resolution = 100;
float ratio = 1.0f / resolution;
float arcLength = 0.0f;
Vector3 p0 = SelectedSpline.Evaluate(0.0f);
List<MultiCurveUtility.ArcTimeLength> arcTimeLengthMap = new List<MultiCurveUtility.ArcTimeLength>();
arcTimeLengthMap.Add(new MultiCurveUtility.ArcTimeLength(0.0f, 0.0f));
for (int i = 1; i <= resolution; i++)
{
float t = ((float)i) * ratio;
Vector3 p1 = SelectedSpline.Evaluate(t);
arcLength += Vector3.Distance(p0, p1);
arcTimeLengthMap.Add(new MultiCurveUtility.ArcTimeLength(t, arcLength));
p0 = p1;
}
float target = u * arcLength;
int low = 0;
int high = 1;
float min = 0.0f;
float max = 0.0f;
for (int i = 1; i < arcTimeLengthMap.Count; i++)
{
max = arcTimeLengthMap[i].ArcLength;
if (target > min && target < max)
{
high = i;
low = i - 1;
break;
}
min = max;
}
float p = (target - min) / (max - min);
float lowTime = arcTimeLengthMap[low].ArcTime;
float highTime = arcTimeLengthMap[high].ArcTime;
float lowHighDelta = highTime - lowTime;
return arcTimeLengthMap[low].ArcTime + (lowHighDelta * p);
}
在上面的代码中,我传递了 0 和 1 之间的时间 (u),以便 return 一个可用于评估立方贝塞尔曲线上代表 x- 的点的时间轴以恒定速率移动。
结果是这样的:
Cubic Bezier Image
红点代表 return 仅使用贝塞尔公式评估原始时间的法线点。黄点表示经过时间后的'constant'速度位置已经逼近。在我开始将切线更改为相当夸张之前,它似乎非常准确。我也试过增加间隔,但没有任何帮助。
无论如何,任何帮助都会很棒。我还不太擅长阅读公式(我确定问题出在哪里),所以请使用代码示例获得一些帮助会很棒。 :>
谢谢!
我没有发现该方法有任何明显的错误,因此将分辨率提高到 1000 或 10000 会有所帮助。
这不是您要的,而是为了提高效率,以防这是一些高性能游戏的一部分,其中包含大量图形和严格的性能要求
a) 一步将值存储在 table 中,然后在单独的一步中访问它们,因此对于该曲线,您不必每次都重新计算 100(0(0)) 个点
b) 使用二分搜索或线性估计正确区间的下一个猜测,而不是单步执行这些值
您还想用 C 语言而不是 Python 来编写它,但显然这是关于 Python。
好吧,我好像自己找到了答案
TLDR;对于二维曲线,不要使用弧长来计算目标距离。仅使用水平(x 轴)长度。
快速注意:如果您的曲线可以沿 x 轴向后移动,则此解决方案可能对您不起作用。我的没有。
具体来说,目标距离,即用于估计我应该在曲线上观察的位置的值,是时间和弧长的乘积。弧长不准确,因为它考虑了 y 轴距离。我只关心水平运动,所以y距离是不必要的。
这是我更新后的代码:
private float GetApproximatedTime(float u)
{
int resolution = 25 * SelectedSpline.CurveCount; // Factor in additional curves.
float ratio = 1.0f / resolution;
float arcLength = 0.0f;
Vector3 p0 = SelectedSpline.Evaluate(0.0f);
List<MultiCurveUtility.ArcTimeLength> arcTimeLengthMap = new List<MultiCurveUtility.ArcTimeLength>();
arcTimeLengthMap.Add(new MultiCurveUtility.ArcTimeLength(0.0f, 0.0f));
for (int i = 1; i <= resolution; i++)
{
float t = ((float)i) * ratio;
Vector3 p1 = SelectedSpline.Evaluate(t);
arcLength += Mathf.Abs(p1.x - p0.x); // Only use the x-axis delta.
arcTimeLengthMap.Add(new MultiCurveUtility.ArcTimeLength(t, arcLength));
p0 = p1;
}
float target = u * arcLength;
int low = 0;
int high = 1;
float min = 0.0f;
float max = 0.0f;
for (int i = 1; i < arcTimeLengthMap.Count; i++)
{
max = arcTimeLengthMap[i].ArcLength;
if (target > min && target < max)
{
high = i;
low = i - 1;
break;
}
min = max;
}
float p = (target - min) / (max - min);
float lowTime = arcTimeLengthMap[low].ArcTime;
float highTime = arcTimeLengthMap[high].ArcTime;
float lowHighDelta = highTime - lowTime;
return arcTimeLengthMap[low].ArcTime + (lowHighDelta * p);
}
请注意这里有两个重大变化:
arcLength += Mathf.Abs(p1.x - p0.x);
和
int resolution = 25 * SelectedSpline.CurveCount;
第二个变化是确保添加曲线时分辨率不会降低。否则,您可能会注意到返回时间的准确性有误。我发现每条曲线 25 的间隔非常准确且非常快。也就是说,此代码中有一些明显的优化,但如果您也无法解决这个问题,它应该适合您。
这是结果的屏幕截图。黄点是我使用新时间评估的点。绿点代表我的高点和低点。
IMAGE - Resulting Graph - Constant Time Over Cubic Bezier Curve
我要解决的问题是我似乎无法以恒定速度沿着三次贝塞尔曲线移动二维点。
我遵循了本教程:http://catlikecoding.com/unity/tutorials/curves-and-splines/ 最初实现了曲线,效果非常好。但是当试图以恒定速度逼近该点时,它的方式,方式。
根据我目前所读的内容,您应该遍历曲线,计算每个步长的弧长和间隔距离。然后,将这些距离与目标距离(弧长 * 时间)进行比较以找到最近的距离。如果分辨率足够高,这应该几乎没有误差并且足够准确以满足我的需要。
这是我目前的代码:
积分计算:
public static Vector3 GetPoint (Vector3 p0, Vector3 p1, Vector3 p2, Vector3 p3, float t)
{
t = Mathf.Clamp01(t);
float oneMinusT = 1f - t;
return
oneMinusT * oneMinusT * oneMinusT * p0 +
3f * oneMinusT * oneMinusT * t * p1 +
3f * oneMinusT * t * t * p2 +
t * t * t * p3;
}
在恒定时间计算点的遗憾尝试:
private float GetApproximatedTime(float u)
{
int resolution = 100;
float ratio = 1.0f / resolution;
float arcLength = 0.0f;
Vector3 p0 = SelectedSpline.Evaluate(0.0f);
List<MultiCurveUtility.ArcTimeLength> arcTimeLengthMap = new List<MultiCurveUtility.ArcTimeLength>();
arcTimeLengthMap.Add(new MultiCurveUtility.ArcTimeLength(0.0f, 0.0f));
for (int i = 1; i <= resolution; i++)
{
float t = ((float)i) * ratio;
Vector3 p1 = SelectedSpline.Evaluate(t);
arcLength += Vector3.Distance(p0, p1);
arcTimeLengthMap.Add(new MultiCurveUtility.ArcTimeLength(t, arcLength));
p0 = p1;
}
float target = u * arcLength;
int low = 0;
int high = 1;
float min = 0.0f;
float max = 0.0f;
for (int i = 1; i < arcTimeLengthMap.Count; i++)
{
max = arcTimeLengthMap[i].ArcLength;
if (target > min && target < max)
{
high = i;
low = i - 1;
break;
}
min = max;
}
float p = (target - min) / (max - min);
float lowTime = arcTimeLengthMap[low].ArcTime;
float highTime = arcTimeLengthMap[high].ArcTime;
float lowHighDelta = highTime - lowTime;
return arcTimeLengthMap[low].ArcTime + (lowHighDelta * p);
}
在上面的代码中,我传递了 0 和 1 之间的时间 (u),以便 return 一个可用于评估立方贝塞尔曲线上代表 x- 的点的时间轴以恒定速率移动。
结果是这样的: Cubic Bezier Image
红点代表 return 仅使用贝塞尔公式评估原始时间的法线点。黄点表示经过时间后的'constant'速度位置已经逼近。在我开始将切线更改为相当夸张之前,它似乎非常准确。我也试过增加间隔,但没有任何帮助。
无论如何,任何帮助都会很棒。我还不太擅长阅读公式(我确定问题出在哪里),所以请使用代码示例获得一些帮助会很棒。 :>
谢谢!
我没有发现该方法有任何明显的错误,因此将分辨率提高到 1000 或 10000 会有所帮助。
这不是您要的,而是为了提高效率,以防这是一些高性能游戏的一部分,其中包含大量图形和严格的性能要求
a) 一步将值存储在 table 中,然后在单独的一步中访问它们,因此对于该曲线,您不必每次都重新计算 100(0(0)) 个点
b) 使用二分搜索或线性估计正确区间的下一个猜测,而不是单步执行这些值
您还想用 C 语言而不是 Python 来编写它,但显然这是关于 Python。
好吧,我好像自己找到了答案
TLDR;对于二维曲线,不要使用弧长来计算目标距离。仅使用水平(x 轴)长度。
快速注意:如果您的曲线可以沿 x 轴向后移动,则此解决方案可能对您不起作用。我的没有。
具体来说,目标距离,即用于估计我应该在曲线上观察的位置的值,是时间和弧长的乘积。弧长不准确,因为它考虑了 y 轴距离。我只关心水平运动,所以y距离是不必要的。
这是我更新后的代码:
private float GetApproximatedTime(float u)
{
int resolution = 25 * SelectedSpline.CurveCount; // Factor in additional curves.
float ratio = 1.0f / resolution;
float arcLength = 0.0f;
Vector3 p0 = SelectedSpline.Evaluate(0.0f);
List<MultiCurveUtility.ArcTimeLength> arcTimeLengthMap = new List<MultiCurveUtility.ArcTimeLength>();
arcTimeLengthMap.Add(new MultiCurveUtility.ArcTimeLength(0.0f, 0.0f));
for (int i = 1; i <= resolution; i++)
{
float t = ((float)i) * ratio;
Vector3 p1 = SelectedSpline.Evaluate(t);
arcLength += Mathf.Abs(p1.x - p0.x); // Only use the x-axis delta.
arcTimeLengthMap.Add(new MultiCurveUtility.ArcTimeLength(t, arcLength));
p0 = p1;
}
float target = u * arcLength;
int low = 0;
int high = 1;
float min = 0.0f;
float max = 0.0f;
for (int i = 1; i < arcTimeLengthMap.Count; i++)
{
max = arcTimeLengthMap[i].ArcLength;
if (target > min && target < max)
{
high = i;
low = i - 1;
break;
}
min = max;
}
float p = (target - min) / (max - min);
float lowTime = arcTimeLengthMap[low].ArcTime;
float highTime = arcTimeLengthMap[high].ArcTime;
float lowHighDelta = highTime - lowTime;
return arcTimeLengthMap[low].ArcTime + (lowHighDelta * p);
}
请注意这里有两个重大变化:
arcLength += Mathf.Abs(p1.x - p0.x);
和
int resolution = 25 * SelectedSpline.CurveCount;
第二个变化是确保添加曲线时分辨率不会降低。否则,您可能会注意到返回时间的准确性有误。我发现每条曲线 25 的间隔非常准确且非常快。也就是说,此代码中有一些明显的优化,但如果您也无法解决这个问题,它应该适合您。
这是结果的屏幕截图。黄点是我使用新时间评估的点。绿点代表我的高点和低点。
IMAGE - Resulting Graph - Constant Time Over Cubic Bezier Curve