Поверните передние колеса автомобиля по траектории Безье.

У меня есть 3D-автомобиль, который следует заранее заданному 3D-пути Безье. Я хочу, чтобы вращение передних колес автомобиля соответствовало изменению направления движения автомобиля.

У меня возникла идея сопоставить ориентацию колеса с производной направления пути (трехмерным вектором), также известной как производная 2-й степени от пути Безье.

По какой-то причине это практически не работает. В какой-то момент вроде работает нормально, а в какой-то чертовски колесо крутится. Я заметил, что производная 2-й степени изменяется, даже если путь Безье является прямой: AFAIK в этом случае должен быть 0.

Итак, мой 1-й вопрос: верна ли моя идея согласовать вращение колеса со 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;
    }

}
}

А вот код, который применяет производную ориентацию 2-й степени к колесам автомобиля:

            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 градусов! 1st OK2-я ошибка

РЕДАКТИРОВАТЬ: Вот пример, где Безье вырожден в прямую линию (все 4 точки принадлежат прямой)! Поскольку в случае прямой линии направление f '(t) постоянно, не должна ли его производная f' '(t) быть всегда нулевой?

Например, для якоря1, якоря2, элемента управления1, элемента управления2 соответственно:

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)

person Bill Kotsias    schedule 23.04.2015    source источник
comment
Обычно мы не используем второй производный вектор пути для выравнивания объектов. Мы либо используем касательную к пути (то есть единичный вектор первой производной), либо нормальное направление пути (которое получается как из первой производной, так и из 2-й производной).   -  person fang    schedule 24.04.2015
comment
Я использую 1-ю производную, чтобы сориентировать машину, и да, она отлично работает. Очевидно, я не могу использовать его и для шин одновременно. У тебя есть другие идеи?   -  person Bill Kotsias    schedule 24.04.2015
comment
Из ваших кодов и изображения мне непонятно, как вы определяете ориентацию колеса. Но если я нахожусь в вашем положении, я буду использовать нормаль пути, чтобы определить ориентацию колеса. Если вы рассматриваете колесо как цилиндр, покрытый двумя плоскими гранями, то нормаль пути будет перпендикулярна двум плоским граням.   -  person fang    schedule 24.04.2015
comment
Ориентация колеса задается 3D-движком, который я использую: public function setOrientation (dir: Vector3D, up: Vector3D = (0,1,0)): void; where (dir: Vector3D = Direction набор для ориентации). Я считаю колесо, как вы говорите. Вы можете дать ссылку на нормальный путь? Все, что я получаю в Google, - это направление пути (1-я производная, которую я уже использую для ориентации машины)   -  person Bill Kotsias    schedule 24.04.2015
comment
Вычислить бинормальный вектор b = unit_vector (C 'x C), где C' и C - первая и вторая производные векторы пути C (t). Затем вычислите вектор нормали n = b x t, где t - единичный касательный вектор, а x - операция перекрестного произведения.   -  person fang    schedule 24.04.2015
comment
Итак, unit_vector - это функция векторной нормализации, а в n = b x t t = unit_vector (C '), верно?   -  person Bill Kotsias    schedule 24.04.2015
comment
каковы координаты прямой линии и какие производные вы видите в t = 0, t = yourpoint и t = 1 для примера, который вы показываете, когда что-то идет не так? В качестве комментария к использованию нормали: нет никакой разницы между выравниванием оси по нормали или колесом по производной. Если один работает, другой тоже должен работать.   -  person Mike 'Pomax' Kamermans    schedule 24.04.2015
comment
@ Mike'Pomax'Kamermans Добавил значения, чтобы вы могли точно увидеть, что происходит не так. При t = 0,5 f '' (t) меняет знак, но я не понимаю, почему он не должен быть 0 все время! Пожалуйста, расскажите немного об использовании нормального. Мне бы очень хотелось получить пару ответов, чем длинный список комментариев !!!   -  person Bill Kotsias    schedule 24.04.2015
comment
кубические кривые - это полиномы 3-го порядка, поэтому производные (для каждого измерения) представляют собой квадратичную кривую, вторая производная - прямая линия, а третья производная - скалярная константа. Несмотря на то, что кубическая кривая представляет собой прямую линию, формула для нее является многочленом 3-го порядка, а производная - тремя квадратичными многочленами, которые (должны) быть одинаковыми для всех значений t. Как кубический Безье, производная на самом деле будет больше в конечных точках, но ориентация должна быть сохранена ... позвольте мне проверить, какой она должна быть.   -  person Mike 'Pomax' Kamermans    schedule 24.04.2015
comment
@ Mike'Pomax'Kamermans Вы можете посмотреть здесь, функции намного легче читать с математической нотацией: math.stackexchange.com/questions/1250070/ < / а>   -  person Bill Kotsias    schedule 24.04.2015
comment
Я оставил вам здесь ответ, но для протокола нас не интересует вторая производная. Мы заботимся только о первом. Тем не менее, ваши вторые производные числа верны, поэтому понятно, что ваши первые производные числа также будут правильными. Если вы используете эти вторые производные числа, это объясняет, почему вы видите переворот на t=0.5, поскольку именно там ускорение меняет знак.   -  person Mike 'Pomax' Kamermans    schedule 24.04.2015


Ответы (1)


Угол поворота колес по отношению к направлению движения автомобиля связан с кривизной со знаком пути, обычно обозначаемой \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. Радиус кривизны имеет приятное геометрическое значение (связанный с «соприкасающимся кругом» пути), но он становится бесконечным, когда путь представляет собой прямую линию.

Вот одна приблизительная формула, которую я нашел в физической литературе для связи между углом поворота колес и радиусом кривизны: 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). Ясно, что обе формулы не могут быть правильными. Я не уверен, какой из них правильный. Просто попробуйте их оба или найдите подходящее лечение в литературе по физике.

Еще одна вещь, о которой нужно подумать: в какой точке вдоль машины измерять кривизну. Опять же, есть (по крайней мере) два варианта выбора: переднее или заднее, и я не уверен, какой из них правильный. Думаю задние колеса.

Если ни один из этих вариантов не сработает, возможно, я ошибся. Дайте мне знать, и я проверю свою работу.

person Edward Doolittle    schedule 24.04.2015