Как убедиться, что Math.acos получает значение в диапазоне [-1..1] - PullRequest
4 голосов
/ 04 апреля 2019

Расчет угла между двумя векторами, которые вы должны использовать Math.acos ( РЕДАКТИРОВАНИЕ : оказывается, что не нужно использовать Math.acos, поскольку существует Math.atan2 -путь), который принимает только значения в диапазоне [-1..1]. Но если vector1.x==vector2.x и vector1.y==vector2.y И из-за природы JS, это приводит к ситуациям, когда 0.1+0.2>0.3 иногда Math.acos получает что-то >1 и, что неудивительно, возвращает NaN.

Я решаю это с помощью проверки перед всеми вызовами и if (v1.x==v2.x&&v1.y==v2.y) я просто return 0 И if (v1.x==-v2.x&&v1.y==-v2.y) return Math.PI.

Я сделал это так ( РЕДАКТИРОВАТЬ в принятом ответе имеет лучшую версию):

function angle(origin, p1, p2, sign=false){
    if (p1.x==p2.x && p1.y==p2.y) return 0
    if (p1.x==-p2.x && p1.y==-p2.y) return Math.PI
    const a = {x: p1.x-origin.x, y: p1.y-origin.y}
    const b = {x: p2.x-origin.x, y: p2.y-origin.y}
    sign = sign && a.x*b.y < a.y*b.x ? -1 : 1
    return sign * Math.acos(
        (a.x*b.x+a.y*b.y)/(Math.sqrt(a.x**2+a.y**2)*Math.sqrt(b.x**2+b.y**2))
    )
}

Это работает, но если есть лучший и более эффективный способ, чем куча проверок логики средней сложности со скоростью ~ 60 кадров в секунду, если вы делаете динамическую графику?

Ответы [ 2 ]

2 голосов
/ 04 апреля 2019

Не используйте acos, используйте atan2 .Чтобы acos работал, вы должны нормализовать разницу и по-разному обрабатывать угол> pi.С atan2 вы просто задаете разницу координат y, x, а остальное корректно обрабатывается.

РЕДАКТИРОВАТЬ

Вы искали угол между векторами, а не аргумент вектора разности, мой плохой.Нам просто нужно изменить способ обработки ошибок в расчетах.С некоторым псевдокодом:

function angle_between(ax, ay, bx, by) {
    var al = ax*ax+ay*ay;
    var bl = bx*bx+by*by;
    var dot = (ax*bx+ay*by)/Math.sqrt(al2*bl2);
    if (dot >= 1) return 0;
    if (dot <= -1) return Math.PI;
    return Math.acos(dot);
}

EDIT2

Хорошо, давайте посмотрим на решение atan2 тоже.Как отметил @njuffa, atan2 все еще можно использовать для вычисления угла между двумя векторами.Еще один квадратный корень, что хорошо.Это также дает нам угол со знаком, что даже лучше для некоторых приложений.

function signed_angle_between(ax, ay, bx, by) {
    var dot = ax*bx + ay*by;
    var cross = ax*by - ay*bx;
    return Math.atan2(cross, dot);
}

console.log(signed_angle_between(3, 4, 30, 40));
console.log(signed_angle_between(2, 5, -50, 20));
console.log(signed_angle_between(2, 5, 50, -20));
console.log(signed_angle_between(1, 1, -1, -1));
1 голос
/ 05 апреля 2019

Вычисления, основанные на acos, не только имеют проблему с ложным NaN, вызванным ошибками округления, приводящими к тому, что его аргумент превышает единицу, но также имеют числовые проблемы для результатов, близких к 0 и близким к π, что приводит к неточным результатам.

Превосходящий подход, который позволяет избежать обеих проблем, основан на atan2: angle (a, b) = atan2 (| a × b |, a · b).Я не знаю Javascript, но ожидаю, что следующий код ISO-C, реализующий это, в значительной степени переведет один в один в Javascript:

double angle (double ax, double ay, double bx, double by)
{
    double dot = ax * bx + ay * by;
    double norm_cross = fabs (ax * by - ay * bx);
    return atan2 (norm_cross, dot);
}

По моему опыту, вычисления с использованием atan2 должны иметьпримерно такая же производительность, как при вычислении с помощью acos, но, конечно, если это будет зависеть от специфики используемой математической библиотеки.

...