Давайте спочатку визначимо нове число. Не хвилюйтесь, це легко.
Або, простіше кажучи: f = √3 × i , при цьому я є уявною одиницею . При цьому обертання на 60 градусів за годинниковою стрілкою таке саме, як множення на 1/2 × (1 - f ) , а обертання на 60 градусів проти годинникової стрілки таке ж, як множення на 1/2 × (1 + f ) . Якщо це звучить дивно, пам’ятайте, що множення на складне число те саме, що обертання в площині 2D. Ми просто трохи «збиваємо» складні числа в уявному напрямку (на √3), щоб не мати справу з квадратними коренями ... або з не цілими числами.
Ми також можемо записати точку (a, b) у вигляді + b × f .
Це дозволяє нам обертати будь-яку точку в площині; наприклад, точка (2,0) = 2 + 0 × f обертається до (1, -1), потім до (-1, -1), (-2,0), (-1,1), ( 1,1) і, нарешті, повернемось до (2,0), просто помноживши його.
Звичайно, нам потрібен спосіб перевести ці точки від наших координат до тих, у яких ми робимо обертання, а потім знову повернутись. Для цього потрібен ще один біт інформації: Якщо точка, яку ми робимо обертанням, знаходиться вліво або вправо від вертикальної лінії. Для простоти ми заявляємо, що має значення "коливання" w 0, якщо воно ліворуч від нього (наприклад, центр обертання [0,0] у ваших двох нижньому зображенні), а також 1, якщо справа з нього. Це розширює наші вихідні точки тривимірними; ( x , y , w ), причому "w" після нормалізації буде або 0, або 1. Функцією нормалізації є:
NORM: ( x , y , w ) -> ( x + floor ( w / 2), y , w mod 2), при цьому операція "mod" визначена таким чином, що вона повертає лише позитивні значення або нуль.
Наш алгоритм виглядає так:
Переведіть наші точки ( a , b , c ) у їх положення відносно центру обертання ( x , y , w ), обчисливши ( a - x , b - y , c - w ), після чого нормалізуючи результат. Це очевидно ставить обертальний центр у (0,0,0).
Перетворіть наші точки від їх "рідних" координат до обертальних складних: ( a , b , c ) -> (2 × a + c , b ) = 2 × a + c + b × f
Поверніть наші точки, помноживши їх на одне з обертових чисел вище, за необхідності.
Ра-перетворіть точки назад від обертальних координат до їх "рідних": ( r , s ) -> (пол ( r / 2), s , r mod 2), з "mod", визначеним вище.
Повторно перетворіть точки назад у вихідне положення, додавши їх до центру обертання ( x , y , z ) та нормалізуючи.
Проста версія наших "триплексних" чисел на основі f в C ++ виглядатиме так:
class hex {
public:
int x;
int y;
int w; /* "wobble"; for any given map, y+w is either odd or
even for ALL hexes of that map */
hex(int x, int y, int w) : x(x), y(y), w(w) {}
/* rest of the implementation */
};
class triplex {
public:
int r; /* real part */
int s; /* f-imaginary part */
triplex(int new_r, int new_s) : r(new_r), s(new_s) {}
triplex(const hex &hexfield)
{
r = hexfield.x * 2 + hexfield.w;
s = hexfield.y;
}
triplex(const triplex &other)
{
this->r = other.r; this->s = other.s;
}
private:
/* C++ has crazy integer division and mod semantics. */
int _div(int a, unsigned int b)
{
int res = a / b;
if( a < 0 && a % b != 0 ) { res -= 1; }
return res;
}
int _mod(int a, unsigned int b)
{
int res = a % b;
if( res < 0 ) { res += a; }
return res;
}
public:
/*
* Self-assignment operator; simple enough
*/
triplex & operator=(const triplex &rhs)
{
this->r = rhs.r; this->s = rhs.s;
return *this;
}
/*
* Multiplication operators - our main workhorse
* Watch out for overflows
*/
triplex & operator*=(const triplex &rhs)
{
/*
* (this->r + this->s * f) * (rhs.r + rhs.s * f)
* = this->r * rhs.r + (this->r * rhs.s + this->s * rhs.r ) * f
* + this->s * rhs.s * f * f
*
* ... remembering that f * f = -3 ...
*
* = (this->r * rhs.r - 3 * this->s * rhs.s)
* + (this->r * rhs.s + this->s * rhs.r) * f
*/
int new_r = this->r * rhs.r - 3 * this->s * rhs.s;
int new_s = this->r * rhs.s + this->s * rhs.r;
this->r = new_r; this->s = new_s;
return *this;
}
const triplex operator*(const triplex &other)
{
return triplex(*this) *= other;
}
/*
* Now for the rotations ...
*/
triplex rotate60CW() /* rotate this by 60 degrees clockwise */
{
/*
* The rotation is the same as multiplikation with (1,-1)
* followed by halving all values (multiplication by (1/2, 0).
* If the values come from transformation from a hex field,
* they will always land back on the hex field; else
* we might lose some information due to the last step.
*/
(*this) *= triplex(1, -1);
this->r /= 2;
this->s /= 2;
}
triplex rotate60CCW() /* Same, counter-clockwise */
{
(*this) *= triplex(1, 1);
this->r /= 2;
this->s /= 2;
}
/*
* Finally, we'd like to get a hex back (actually, I'd
* typically create this as a constructor of the hex class)
*/
operator hex()
{
return hex(_div(this->r, 2), this->s, _mod(this->r, 2));
}
};