Давайте сначала определим новый номер. Не беспокойтесь, это легко.
Или, проще говоря: f = √3 × i , где i - мнимая единица . При этом поворот на 60 градусов по часовой стрелке - это то же самое, что умножение на 1/2 × (1 - f ) , а поворот на 60 градусов против часовой стрелки - на умножение на 1/2 × (1 + f ) . Если это звучит странно, помните, что умножение на комплексное число совпадает с вращением в 2D-плоскости. Мы просто "раздавливаем" комплексные числа в воображаемом направлении немного (на √3), чтобы не иметь дело с квадратными корнями ... или нецелыми числами, если на то пошло.
Мы также можем записать точку (a, b) как a + b × f .
Это позволяет нам вращать любую точку на плоскости; например, точка (2,0) = 2 + 0 × f поворачивается к (1, -1), затем к (-1, -1), (-2,0), (-1,1), ( 1,1) и, наконец, вернуться к (2,0), просто умножив его.
Конечно, нам нужен способ перевести эти точки из наших координат в те, в которых мы делаем повороты, и затем обратно. Для этого нужен еще один бит информации: если точка, вокруг которой мы вращаемся, находится «влево» или «вправо» от вертикальной линии. Для простоты мы объявляем, что оно имеет значение «колебания» w, равное 0, если оно находится слева от него (как центр вращения [0,0] на двух ваших нижних рисунках), и 1, если оно находится справа этого Это расширяет наши оригинальные точки, чтобы быть трехмерным; ( х , у , ш ), где "w" равно 0 или 1 после нормализации. Функция нормализации:
НОРМА: ( x , y , w ) -> ( x + этаж ( 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 ) -> (floor ( 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));
}
};