Войти
ПрограммированиеФорумГрафика

Поворот трехмерного вектора в плоскости заданной двумя векторами.

#0
(Правка: 13:28) 13:24, 14 мая 2019

Доброго времени суток.

Задача: даны два трехмерных вектора - A и B. Данные вектора лежат в одной плоскости. Написать метод поворачивающий вектор A в плоскости задаваемой векторами A и B. Поворот в сторону от вектора A к вектору B считается положительным. Метод работает с градусами, а не с радианами. Ось Y направлена вверх.

Как я решил задачу:
Я создал класс Vector3D и включил в него следующий метод

void Vector3D::rotate(Vector3D* b, double angle) {
  /* 
  * Находим вектор С лежащий в той же плоскости, что и вектора A и B. При этом
  * вектор С будет ортогонален вектору A и ближайший угол поворота вектора А в сторону вектора С
  * будет иметь тот же знак что и ближайший угол поворота вектора A в сторону вектора B.
  * Для этого сперва найдем через векторное произведение - вектор temp, ортогональный векторам A и B,
  * а затем найдем вектор C через векторное произведение векторов A и temp;
  */
  double tempX = y*b->z - z*b->y;
  double tempY = z*b->x - x*b->z;
  double tempZ = x*b->y - y*b->x;
  double cX = tempY*z - tempZ*y;
  double cY = tempZ*x - tempX*z;
  double cZ = tempX*y - tempY*x;
  
  // Нормализуем вектора A и C
  double lengthA = getLength();
  double lengthC = sqrt(cX*cX + cY*cY + cZ*cZ);
  double normAX = x / lengthA;
  double normAY = y / lengthA;
  double normAZ = z / lengthA;
  double normCX = cX / lengthC;
  double normCY = cY / lengthC;
  double normCZ = cZ / lengthC;
  
  // Находим координаты вектора, в локальной системе координат задаваемой векторами A и C.
  double localX = cos(degreeToRadian(angle));
  double localY = sin(degreeToRadian(angle));
  
  // Умножаем векторы A и B на скаляры - длины компонентов результатирующего вектора
  double newXA = normAX * localX;
  double newYA = normAY * localX;
  double newZA = normAZ * localX;
  double newXC = normCX * localY;
  double newYC = normCY * localY;
  double newZC = normCZ * localY;
  
  // Складываем полученные векторы
  x = newXA + newXC;
  y = newYA + newYC;
  z = newZA + newZC;
  
  // возвращаем нашему вектору прежнюю длину умножая его на скаляр.
  x *= lengthA;
  y *= lengthA;
  z *= lengthA;
}

Что не понятно: скажите пожалуйста - как можно уменьшить кол-во операций в методе? Какие шаги алгоритма можно оптимизировать?


#1
14:26, 14 мая 2019

https://en.wikipedia.org/wiki/Rodrigues%27_rotation_formula
или можно взять axis + angle

#2
15:18, 14 мая 2019

Bakuard
http://www.gamedev.ru/articles/?id=30129&page=4
shortest arc rotation

#3
19:28, 14 мая 2019

Bakuard
Если a,b перпендикулярны и равны по длине

c=a*cos(fi)+b*sin(fi)
Если a,b не перпендикулярны, привести b к перпендикуляру
b-=a*dot(a,b)/a.len2(); // len2 - квадрат длины вектора
b*=sqrt(a.len2()/b.len2());
#4
19:57, 14 мая 2019

В первую очередь я бы заменил

  double lengthA = getLength();
  double lengthC = sqrt(cX*cX + cY*cY + cZ*cZ);
  double normAX = x / lengthA;
  double normAY = y / lengthA;
  double normAZ = z / lengthA;
  double normCX = cX / lengthC;
  double normCY = cY / lengthC;
  double normCZ = cZ / lengthC;
на
  double IlengthA = 1.0 / getLength();
  double IlengthC = 1.0 / sqrt(cX*cX + cY*cY + cZ*cZ);
  double normAX = x * IlengthA;
  double normAY = y * IlengthA;
  double normAZ = z * IlengthA;
  double normCX = cX * IlengthC;
  double normCY = cY * IlengthC;
  double normCZ = cZ * IlengthC;

#5
(Правка: 23:27) 22:22, 14 мая 2019

Mikle
Тут можно еще больше сократить код.

Bakuard
> x *= lengthA;
> y *= lengthA;
> z *= lengthA;
Можно убрать последнюю операцию возвращение масштаба, если вместо

  double lengthA = getLength();
  double lengthC = sqrt(cX*cX + cY*cY + cZ*cZ);
  double normAX = x / lengthA;
  double normAY = y / lengthA;
  double normAZ = z / lengthA;
  double normCX = cX / lengthC;
  double normCY = cY / lengthC;
  double normCZ = cZ / lengthC;
Написать
  double IlengthC = getLength() / sqrt(cX*cX + cY*cY + cZ*cZ);
  double normAX = x;
  double normAY = y;
  double normAZ = z;
  double normCX = cX * IlengthC;
  double normCY = cY * IlengthC;
  double normCZ = cZ * IlengthC;

Целиком это бы выглядело так

void Vector3D::rotate(Vector3D* b, double angle) {

  double tempX = y*b->z - z*b->y;
  double tempY = z*b->x - x*b->z;
  double tempZ = x*b->y - y*b->x;
  double cX = tempY*z - tempZ*y;
  double cY = tempZ*x - tempX*z;
  double cZ = tempX*y - tempY*x;
  
  double localX = cos(degreeToRadian(angle));
  double localY = sin(degreeToRadian(angle)) * sqrt((x*x+y*y+z*z) / (cX*cX + cY*cY + cZ*cZ));
  
  x = x * localX + cX * localY;
  y = y * localX + cY * localY;
  z = z * localX + cZ * localY;
}

degreeToRadian лучше делать константой.

#6
22:46, 14 мая 2019

А degreeToRadian я бы вообще выкинул и забыл, всё должно быть в радианах, в градусы переводить только в случаях вывода каких-то показаний, чисто для пользователя.

#7
(Правка: 23:52) 23:22, 14 мая 2019

Aslan
Чем оно короче, если это тоже самое только развернуто.
Aslan
> b-=a*dot(a,b)/a.len2();
кстати деление тут лишнее.

b=b*a.len2() - a*dot(a,b);
итого 12 умножений 4 сложения 3 вычитания (или деление 3 вычитания 4 сложения 9 умножений), против 12 умножений и 6 вычитаний.
#8
(Правка: 15 мая 2019, 0:34) 23:55, 14 мая 2019

foxes
Я раньше написал и проще, вы считаете перпендикуляр=(AxB)xA, а у меня 2 dot, из которых 1 всеравно нужен далее, и 1 умнож-е вектора:
b-=a*dot(a,b)/a.len2();
Кстати, да, надо умножать не вектор B, а к-т

>кстати деление тут лишнее.
>b=b*a.len2() - a*dot(a,b);
Согласен!

И развернуть
(b*l-a*dot(a,b))^2=b^2*a^4-2*(a,b)^2*a^2+a^2*(a,b)^2=
=a^2*(a^2*b^2-(a,b)^2)

В итоге так:

float a2=a.len2(),b2=b.len2(),ab=dot(a,b),k=sin(fi)/sqrt(a2*b2-ab*ab);
c=(cos(fi)-k*ab)*a+(k*a2)*b;

#9
(Правка: 0:13) 0:05, 15 мая 2019

Aslan
> Я раньше написал и проще, вы считаете перпендикуляр=(AxB)xA, а у меня 2 dot, из
> которых 1 всеравно
Ты тоже считаешь перпендикуляр это все выводиться друг из друга, обе формулы, твоя из перпендикуляров, перпендикуляры из твоей.

> double cX = tempY*z - tempZ*y;
> double cY = tempZ*x - tempX*z;
> double cZ = tempX*y - tempY*x;
Если подставить суда temp.. получится тоже что и
Aslan
> b=b*a2-a*ab;

ПрограммированиеФорумГрафика