根据汽车的贝塞尔曲线转动汽车的前轮

Turn the front wheels of a car according to the car's bezier path

我有一辆 3D 汽车,它遵循预定义的 3D 贝塞尔曲线路径。我想让汽车的前轮旋转与汽车的变化方向相匹配。

我想将轮子的方向与路径方向的导数(3D 向量)相匹配,也就是贝塞尔路径的二阶导数。

出于某种原因,这几乎行不通。在某些时候它似乎工作正常,而在其他时候轮子旋转得像地狱一样。我注意到,即使贝塞尔曲线路径是 直线 ,二阶导数也会发生变化:AFAIK 在这种情况下应该是 0.

所以,我的第一个问题是我的想法是否可以将轮子的旋转匹配到第 2 度。如果是,我的第二个问题到底是哪里出了问题?

这是我的贝塞尔 3D 曲线代码:

package fanlib.math {

import flash.geom.Vector3D;

public class BezierCubic3D
{
    public const anchor1:Vector3D = new Vector3D();
    public const anchor2:Vector3D = new Vector3D();
    public const control1:Vector3D = new Vector3D();
    public const control2:Vector3D = new Vector3D();
    /**
     * Gets values from both 'getPointAt' and 'getDirectionAt'
     */
    public const result:Vector3D = new Vector3D();
    private const previous:Vector3D = new Vector3D(); // temporary (optimization)

    // normalization aka arc-parameterization
    public var arcLengths:Vector.<Number> = new Vector.<Number>;
    public var steps:Number = 100;

    private var _length:Number;

    public function BezierCubic3D()
    {
    }

    /**
     * To get a point between anchor1 and anchor2, pass value [0...1]
     * @param t
     */
    public function getPointAt(t:Number):Vector3D {
        const t2:Number = t*t;
        const t3:Number = t*t2;
        const threeT:Number = 3*t;
        const threeT2:Number = 3*t2;
        result.x = getPointAxisAt(anchor1.x, anchor2.x, control1.x, control2.x, t3, threeT, threeT2);
        result.y = getPointAxisAt(anchor1.y, anchor2.y, control1.y, control2.y, t3, threeT, threeT2);
        result.z = getPointAxisAt(anchor1.z, anchor2.z, control1.z, control2.z, t3, threeT, threeT2);
        return result;
    }
    public function getPointAxisAt(a1:Number,a2:Number,c1:Number,c2:Number, t3:Number, threeT:Number, threeT2:Number):Number {
        return  t3      * (a2+3*(c1-c2)-a1) +
                threeT2 * (a1-2*c1+c2) +
                threeT  * (c1-a1) +
                a1;
    }

    /**
     * @param t
     * @return Un-normalized Vector3D! 
     */
    public function getDirectionAt(t:Number):Vector3D {
        const threeT2:Number = 3 * t * t;
        const sixT:Number = 6 * t;
        result.x = getDirAxisAt(anchor1.x, anchor2.x, control1.x, control2.x, threeT2, sixT);
        result.y = getDirAxisAt(anchor1.y, anchor2.y, control1.y, control2.y, threeT2, sixT);
        result.z = getDirAxisAt(anchor1.z, anchor2.z, control1.z, control2.z, threeT2, sixT);
        return result;
    }
    public function getDirAxisAt(a1:Number,a2:Number,c1:Number,c2:Number, threeT2:Number, sixT:Number):Number {
        return  threeT2 * (a2+3*(c1-c2)-a1) +
                sixT    * (a1-2*c1+c2) +
                3       * (c1-a1);
    }

    public function getDirectionDerivativeAt(t:Number):Vector3D {
        const sixT:Number = 6 * t;
        result.x = getDirDerAxisAt(anchor1.x, anchor2.x, control1.x, control2.x, sixT);
        result.y = getDirDerAxisAt(anchor1.y, anchor2.y, control1.y, control2.y, sixT);
        result.z = getDirDerAxisAt(anchor1.z, anchor2.z, control1.z, control2.z, sixT);
        return result;
    }
    public function getDirDerAxisAt(a1:Number,a2:Number,c1:Number,c2:Number, sixT:Number):Number {
        return  sixT    * (a2+3*(c1-c2)-a1) +
                6       * (a1-2*c1+c2);
    }

    /**
     * Call this after any change to defining points and before accessing normalized points of curve.
     */
    public function recalc():void {
        arcLengths.length = steps + 1;
        arcLengths[0] = 0;
        const step:Number = 1 / steps;

        previous.copyFrom(getPointAt(0));
        _length = 0;
        for (var i:int = 1; i <= steps; ++i) {
            _length += Vector3D.distance(getPointAt(i * step), previous);
            arcLengths[i] = _length;
            previous.copyFrom(result);
        }
    }

    /**
     * 'recalc' must have already been called if any changes were made to any of the defining points 
     * @param u
     * @return u normalized/converted to t
     */
    public function normalizeT(u:Number):Number {
        var targetLength:Number = u * arcLengths[steps];
        var low:int = 0,
            high:int = steps,
            index:int; // TODO : have a look-up table of starting low/high indices for each step!
        while (low < high) {
            index = low + ((high - low) >>> 1);
            if (arcLengths[index] < targetLength) {
                low = index + 1;
            } else {
                high = index;
            }
        }
        if (this.arcLengths[index] > targetLength) {
            --index;
        }
        var lengthBefore:Number = arcLengths[index];
        if (lengthBefore === targetLength) {
            return index / steps;
        } else {
            return (index + (targetLength - lengthBefore) / (arcLengths[index + 1] - lengthBefore)) / steps;
        }
    }

    public function getNormalizedPointAt(u:Number):Vector3D {
        return getPointAt(normalizeT(u));
    }

    /**
     * "Normalized" goes for t, not the return Vector3D!!! 
     * @param u
     * @return Un-normalized Vector3D!
     */
    public function getNormalizedDirectionAt(u:Number):Vector3D {
        return getDirectionAt(normalizeT(u));
    }

    public function getNormalizedDirectionDerivativeAt(u:Number):Vector3D {
        return getDirectionDerivativeAt(normalizeT(u));
    }

    public function get length():Number
    {
        return _length;
    }

}
}

下面是将二阶导数方向应用于汽车车轮的代码:

            const dirDer:Vector3D = bezier.getDirectionDerivativeAt(time);
            dirDer.negate(); // negate vector's values; for some reason, this gives better results
            for each (wheel in dirWheels) {
                wheel.setRotation(0,0,0); // must nullify before below line
                const localDirDer:Vector3D = wheel.globalToLocalVector(dirDer); // convert dirDer vector to wheel's local axis; wheel translation does NOT affect conversion
                wheel.setOrientation(localDirDer); // orients the object in a specific direction; Up-vector's default value = (0,1,0) 
            }

我什至尝试过(无济于事):

            for each (wheel in dirWheels) {
                const localDirDer:Vector3D = wheel.parent.globalToLocalVector(dirDer); // convert dirDer vector to wheel's local axis; wheel translation does NOT affect conversion
                wheel.setOrientation(localDirDer); // orients the object in a specific direction; Up-vector's default value = (0,1,0) 
            }

一个错误的明显例子:即使汽车在一条直线上,车轮本来是不旋转的(应该的),但是当汽车通过直线的中心点后,车轮旋转180度!

编辑: 下面是贝塞尔退化到一条直线的例子(所有4个点都属于一条直线)!因为,在直线的情况下,方向 f'(t) 是恒定的,它的导数 f''(t) 不应该 总是零

例如对于anchor1、anchor2、control1、control2分别为:​​

Vector3D(-4.01,0.00,-1.90) Vector3D(4.01,0.00,-1.90)
Vector3D(-2.01,0.00,-1.90) Vector3D(2.01,0.00,-1.90)

我明白了

f'(0.08)=Vector3D(-1.00,0.00,0.00) f''(0.08)=Vector3D(10.14,0.00,0.00)
f'(0.11)=Vector3D(-1.00,0.00,0.00) f''(0.11)=Vector3D(9.42,0.00,0.00)
f'(0.15)=Vector3D(-1.00,0.00,0.00) f''(0.15)=Vector3D(8.44,0.00,0.00)
f'(0.18)=Vector3D(-1.00,0.00,0.00) f''(0.18)=Vector3D(7.69,0.00,0.00)
f'(0.21)=Vector3D(-1.00,0.00,0.00) f''(0.21)=Vector3D(6.87,0.00,0.00)
f'(0.24)=Vector3D(-1.00,0.00,0.00) f''(0.24)=Vector3D(6.16,0.00,0.00)
f'(0.27)=Vector3D(-1.00,0.00,0.00) f''(0.27)=Vector3D(5.47,0.00,0.00)
f'(0.30)=Vector3D(-1.00,0.00,0.00) f''(0.30)=Vector3D(4.70,0.00,0.00)
f'(0.33)=Vector3D(-1.00,0.00,0.00) f''(0.33)=Vector3D(4.03,0.00,0.00)
f'(0.36)=Vector3D(-1.00,0.00,0.00) f''(0.36)=Vector3D(3.37,0.00,0.00)
f'(0.39)=Vector3D(-1.00,0.00,0.00) f''(0.39)=Vector3D(2.63,0.00,0.00)
f'(0.42)=Vector3D(-1.00,0.00,0.00) f''(0.42)=Vector3D(1.99,0.00,0.00)
f'(0.44)=Vector3D(-1.00,0.00,0.00) f''(0.44)=Vector3D(1.34,0.00,0.00)
f'(0.47)=Vector3D(-1.00,0.00,0.00) f''(0.47)=Vector3D(0.62,0.00,0.00)
f'(0.50)=Vector3D(-1.00,0.00,0.00) f''(0.50)=Vector3D(-0.02,0.00,0.00)
f'(0.53)=Vector3D(-1.00,0.00,0.00) f''(0.53)=Vector3D(-0.74,0.00,0.00)
f'(0.56)=Vector3D(-1.00,0.00,0.00) f''(0.56)=Vector3D(-1.38,0.00,0.00)
f'(0.58)=Vector3D(-1.00,0.00,0.00) f''(0.58)=Vector3D(-2.03,0.00,0.00)
f'(0.61)=Vector3D(-1.00,0.00,0.00) f''(0.61)=Vector3D(-2.67,0.00,0.00)
f'(0.64)=Vector3D(-1.00,0.00,0.00) f''(0.64)=Vector3D(-3.41,0.00,0.00)
f'(0.67)=Vector3D(-1.00,0.00,0.00) f''(0.67)=Vector3D(-4.07,0.00,0.00)
f'(0.70)=Vector3D(-1.00,0.00,0.00) f''(0.70)=Vector3D(-4.74,0.00,0.00)
f'(0.73)=Vector3D(-1.00,0.00,0.00) f''(0.73)=Vector3D(-5.51,0.00,0.00)
f'(0.76)=Vector3D(-1.00,0.00,0.00) f''(0.76)=Vector3D(-6.20,0.00,0.00)
f'(0.79)=Vector3D(-1.00,0.00,0.00) f''(0.79)=Vector3D(-6.91,0.00,0.00)
f'(0.82)=Vector3D(-1.00,0.00,0.00) f''(0.82)=Vector3D(-7.74,0.00,0.00)
f'(0.85)=Vector3D(-1.00,0.00,0.00) f''(0.85)=Vector3D(-8.49,0.00,0.00)
f'(0.89)=Vector3D(-1.00,0.00,0.00) f''(0.89)=Vector3D(-9.27,0.00,0.00)
f'(0.92)=Vector3D(-1.00,0.00,0.00) f''(0.92)=Vector3D(-10.19,0.00,0.00)
f'(0.96)=Vector3D(-1.00,0.00,0.00) f''(0.96)=Vector3D(-11.06,0.00,0.00)
f'(1.00)=Vector3D(-1.00,0.00,0.00) f''(1.00)=Vector3D(-11.98,0.00,0.00)

车轮相对于汽车方向的角度与路径的有符号曲率有关,通常用\kappa表示。对于弧长参数化曲线,|\kappa| = length of vector dT/ds 其中 T 是单位切向量,dT/ds 是其关于弧长参数的导数。 \kappa 的符号取决于曲线的方向,但是一旦你弄清楚了在一个位置是左还是右是正的,你应该对场景的其余部分有好处。

贝塞尔曲线不是弧长参数化的(除非你做了一些超神奇的事情),所以你将不得不使用更复杂的表达式。对于平面上的路径,您应该使用 \kappa = (x'y''-y'x'')/(x'^2+y'^2)^{3/2}。这很好,因为你不需要弧长参数化,而且它是有符号的,但你仍然需要弄清楚哪个符号表示左或右。

你还要搞清楚轮子的角度和曲率的关系。为此,您可能会找到基于 曲率半径 R = 1/\kappa 的公式。曲率半径有一个很好的几何意义(与路径的"osculating circle"有关),但是当路径是直线时它就变得无穷大了。

这里有一个近似的公式是我在物理学文献中找到的关于轮角和曲率半径之间关系的公式:R = s/sqrt(2-2cos(2A)) 其中s是轴距(前后轮中心之间的距离)和A是车轮的角度。您可以像这样求解 A 的公式:(s/R)^2/2 = 1-cos(2A)(s/(2R))^2 = sin^2(A)s\kappa/2 = sin(A)A = arcsin(s\kappa/2)。这很好地避免了 0 角的奇点。像往常一样,您必须检查标志是否有意义,并在必要时将其反转。

我看到的另一个公式是A=arcsin(s\kappa)。显然这两个公式都不可能是正确的。我不确定哪个是正确的。两者都试一下,或者在物理文献中找到一个好的治疗方法。

您还需要考虑一件事:沿着汽车的哪个点测量曲率。同样,有(至少)两个选择,前轮或后轮,我不确定哪个是正确的。我觉得后轮。

如果这些选择中 none 行得通,我可能犯了一个错误。让我知道,我会检查我的工作。