c++贝塞尔曲线切线方向问题
c++ Bezier curve Tangent direction issues
我在 3D space 中沿着我的三次贝塞尔曲线移动时遇到有关切线的问题,其中使用 (x,z) 而不是 (x,y)。 已添加曲线图像。
当实体击中图像上指示的红线时会出现此问题。切线从 0.0 逐渐开始到 1.0 但随后触及红线并开始从 1.0 逐渐减小到 0.0,导致实体的方向翻转.
目前这个片段负责切线:
const Vector3 Class::getTangent(const float s) const
{
const float t { 1 - s };
return (3 * t * t * (points.at(1) - points.at(0)) +
6 * s * t * (points.at(2) - points.at(1)) +
3 * s * s * (points.at(3) - points.at(2)));
}
然后我将此函数用于:
Vector3 firstDerivative = curve.getTangent(segment);
firstDerivative.normalise();
Vector3 normal = firstDerivative.crossProduct(Vector3(0, 0, 1)); // Z = forward
normal.normalise();
Vector3 b = firstDerivative.crossProduct(normal);
b.normalise();
然后我简单地使用b
来设置实体沿曲线的方向。当达到翻转实体的点时,我确实通过将 b
的符号切换为 -b
来创建 simple/dirty 修复。现在尽管“修复”了这个问题,但我相信如果曲线发生变化,修复会在以后引起问题。
因此,如果有人能发现我可能出错的地方或我可能需要做些什么来解决这个问题,我们将不胜感激。 您能否通过伪代码或 C++ 代码提供针对给定问题的解决方案示例。
这实际上是一个很常见的问题,我在为游戏演示(wipeout97 风格)制作赛道生成器时也遇到过这个问题,后来一位同事在地形工具中为众所周知的 3D 制作道路生成器时也遇到过这个问题商业内容创建工具。
要解决此问题,您需要以下两种可能之一:
- 硬垂直
- 惯性
硬垂直线并不难制作,您只需使用固定向量(例如 (0,1,0))制作您的叉积即可,但是您的曲线永远无法在任何点上准确地沿此方向移动,否则有一天你的叉积变成了空,一切都变得一团糟。
惯性系统迭代地跟随曲线,一开始使用一个参考硬矢量,但只有一次。
然后曲线之后是增量增量,并且每次新的切线和副切线都是通过前一段的切线和副切线(TBN 基础)的渐进改变来发现的。您使用小 t
(曲线参数)增量不会导致巨大的 TBN 改变这一事实,并且您可以使用先前的 TBN 向量之一与新切线进行叉积以找到新的 B 和 N。这样几乎可以保证你永远不会出现这种云台锁定效果。
然后各种可能的有趣参数也是可能的,比如在每一步强制 TBN 基础的最大旋转率,等等,在轨道创建的情况下,这提供了很多艺术力量。
好的,通过一些额外的实验,我设法让它工作,我相信这是一个不错的修复。如果有人能向我解释这是否可行,我将不胜感激。
无论如何,我正在编码的包有两个可用的函数,它们是 setDirection
(最初使用) 和 lookAt
(现在使用)。
代码之前:
entity->setDirection (firstDerivative, WorldSpace, secondDerivative);
之后:
entity->lookAt (point - firstDerivative, WorldSpace);
现在,secondDerivative
现在从未使用过,这完全没问题,但这是否是一个更受欢迎的解决方案?
既然你的贝塞尔曲线是在(x, z)平面上,我不太明白你为什么要在一阶导数和z轴(0, 0, 1)之间取叉积。这样求出的'normal'向量,一阶导数向(0,0,1)方向走时,势必为零。
如果你想找到曲线上任何给定点的法线方向,你应该首先计算一阶导数 C'(t) 和二阶导数 C"(t),然后是法向量 vec(n ) 可以计算为
vec(n) = C' X C" / |C'||C"|
你的双法向量 vec(b) 可以计算为 vec(b) = vec(t) X vec(n) 其中 vec(t) 是单位切向量 vec(t)=C'(t )/|C'(t)|.
我在 3D space 中沿着我的三次贝塞尔曲线移动时遇到有关切线的问题,其中使用 (x,z) 而不是 (x,y)。 已添加曲线图像。
当实体击中图像上指示的红线时会出现此问题。切线从 0.0 逐渐开始到 1.0 但随后触及红线并开始从 1.0 逐渐减小到 0.0,导致实体的方向翻转.
目前这个片段负责切线:
const Vector3 Class::getTangent(const float s) const
{
const float t { 1 - s };
return (3 * t * t * (points.at(1) - points.at(0)) +
6 * s * t * (points.at(2) - points.at(1)) +
3 * s * s * (points.at(3) - points.at(2)));
}
然后我将此函数用于:
Vector3 firstDerivative = curve.getTangent(segment);
firstDerivative.normalise();
Vector3 normal = firstDerivative.crossProduct(Vector3(0, 0, 1)); // Z = forward
normal.normalise();
Vector3 b = firstDerivative.crossProduct(normal);
b.normalise();
然后我简单地使用b
来设置实体沿曲线的方向。当达到翻转实体的点时,我确实通过将 b
的符号切换为 -b
来创建 simple/dirty 修复。现在尽管“修复”了这个问题,但我相信如果曲线发生变化,修复会在以后引起问题。
因此,如果有人能发现我可能出错的地方或我可能需要做些什么来解决这个问题,我们将不胜感激。 您能否通过伪代码或 C++ 代码提供针对给定问题的解决方案示例。
这实际上是一个很常见的问题,我在为游戏演示(wipeout97 风格)制作赛道生成器时也遇到过这个问题,后来一位同事在地形工具中为众所周知的 3D 制作道路生成器时也遇到过这个问题商业内容创建工具。
要解决此问题,您需要以下两种可能之一:
- 硬垂直
- 惯性
硬垂直线并不难制作,您只需使用固定向量(例如 (0,1,0))制作您的叉积即可,但是您的曲线永远无法在任何点上准确地沿此方向移动,否则有一天你的叉积变成了空,一切都变得一团糟。
惯性系统迭代地跟随曲线,一开始使用一个参考硬矢量,但只有一次。
然后曲线之后是增量增量,并且每次新的切线和副切线都是通过前一段的切线和副切线(TBN 基础)的渐进改变来发现的。您使用小 t
(曲线参数)增量不会导致巨大的 TBN 改变这一事实,并且您可以使用先前的 TBN 向量之一与新切线进行叉积以找到新的 B 和 N。这样几乎可以保证你永远不会出现这种云台锁定效果。
然后各种可能的有趣参数也是可能的,比如在每一步强制 TBN 基础的最大旋转率,等等,在轨道创建的情况下,这提供了很多艺术力量。
好的,通过一些额外的实验,我设法让它工作,我相信这是一个不错的修复。如果有人能向我解释这是否可行,我将不胜感激。
无论如何,我正在编码的包有两个可用的函数,它们是 setDirection
(最初使用) 和 lookAt
(现在使用)。
代码之前:
entity->setDirection (firstDerivative, WorldSpace, secondDerivative);
之后:
entity->lookAt (point - firstDerivative, WorldSpace);
现在,secondDerivative
现在从未使用过,这完全没问题,但这是否是一个更受欢迎的解决方案?
既然你的贝塞尔曲线是在(x, z)平面上,我不太明白你为什么要在一阶导数和z轴(0, 0, 1)之间取叉积。这样求出的'normal'向量,一阶导数向(0,0,1)方向走时,势必为零。
如果你想找到曲线上任何给定点的法线方向,你应该首先计算一阶导数 C'(t) 和二阶导数 C"(t),然后是法向量 vec(n ) 可以计算为
vec(n) = C' X C" / |C'||C"|
你的双法向量 vec(b) 可以计算为 vec(b) = vec(t) X vec(n) 其中 vec(t) 是单位切向量 vec(t)=C'(t )/|C'(t)|.