Кватернионы были придуманы Роуэном Уильямом Гамильтоном как альтернатива матричным вращениям. Вращение при помощи кватернионов сводится к умножению чисел, что очень просто в программной реализации.
Алгоритм:
Входные данные:
1) угол θ на который производится вращение.
2) координаты x, y, z вращаемой точки.
3) координаты i, j, k направляющего единичного вектора, вокруг которого происходит вращение.
На выходе алгоритм выдает кватернион, содержащий координаты точки, обращенной вокруг заданного вектора ПО часовой стрелке его направления.
Процедура:
1) присвоить q <- (cos(θ/2), i*sin(θ/2), j*sin(θ/2), k*sin(θ/2)), где i,j,k - координаты единичного вектора, вокруг которого вращаем координату.
2) присвоить p <- (0, x, y, z), где x,y,z - координаты вращаемой точки.
3) присвоить p <- q*p
4) присвоить p <- p*q^-1
Кватернион p теперь имеет координаты (0, x', y', z'), где x',y',z' - новые координаты.
Хочу напомнить что умножение кватернионов не коммутативно, т.е. a*b != b*a
Реализация кватернионов для вращения даже не требует их сложения, поэтому реализация очень проста (реализованы базовые операции: модуль, умножение, обратный кватернион):
package net.ilyi; public class Quaternion { public double r, i, j, k; public Quaternion(double r, double i, double j, double k) { this.r = r; this.i = i; this.j = j; this.k = k; } public double mag() { return Math.sqrt(r*r + i*i + j*j + k*k); } public Quaternion inverse() { double mag = this.mag(); return new Quaternion(r/mag, -i/mag, -j/mag, -k/mag); } public Quaternion unit() { double mag = this.mag(); return new Quaternion(r, i/mag, j/mag, k/mag); } public Quaternion mul(Quaternion q) { double r = this.r*q.r - this.i*q.i - this.j*q.j - this.k*q.k; double i = this.r*q.i + this.i*q.r + this.j*q.k - this.k*q.j; double j = this.r*q.j - this.i*q.k + this.j*q.r + this.k*q.i; double k = this.r*q.k + this.i*q.j - this.j*q.i + this.k*q.r; return new Quaternion(r, i, j, k); } }
А вот код, который реализует картинку статьи, без использования матричных вращений:
double phi = Math.PI/180.0*angle; double i = 0.0; double j = 1.0; double k = 0.0; double cosphi = Math.cos(phi/2.0); double sinphi = Math.sin(phi/2.0); Quaternion q = new Quaternion(cosphi, i*sinphi, j*sinphi, k*sinphi).unit(); Quaternion p; // triangle glTranslatef(0.0f, 0.0f, -2.0f); glBegin(GL_TRIANGLES); glColor3f(1.0f, 0.0f, 0.0f); p = new Quaternion(0.0, -0.5, -0.5, 0.0); p = q.mul(p.mul(q.inverse())); glVertex3f((float)p.i, (float)p.j, (float)p.k); glColor3f(0.0f, 1.0f, 0.0f); p = new Quaternion(0.0, 0.5, -0.5, 0.0); p = q.mul(p.mul(q.inverse())); glVertex3f((float)p.i, (float)p.j, (float)p.k); glColor3f(0.0f, 0.0f, 1.0f); p = new Quaternion(0.0, 0.5, 0.5, 0.0); p = q.mul(p.mul(q.inverse())); glVertex3f((float)p.i, (float)p.j, (float)p.k); glEnd(); angle += 0.1f * delta;