Najpierw zdefiniujmy nowy numer. Nie martw się, to łatwe.
Lub, mówiąc prosto: f = √3 × i , przy czym i jest jednostką urojoną . Dzięki temu obrót o 60 stopni w kierunku zgodnym z ruchem wskazówek zegara jest taki sam jak pomnożenie przez 1/2 × (1 - f ) , a obrót o 60 stopni w kierunku przeciwnym do ruchu wskazówek zegara jest taki sam jak pomnożenie przez 1/2 × (1 + f ) . Jeśli brzmi to dziwnie, pamiętaj, że mnożenie przez liczbę zespoloną jest takie samo jak obrót w płaszczyźnie 2D. Po prostu „wyciskamy” liczby zespolone w wyobrażonym kierunku nieco (o √3), aby nie mieć do czynienia z pierwiastkami kwadratowymi ... ani z liczbami całkowitymi.
Możemy również zapisać punkt (a, b) jako a + b × f .
To pozwala nam obrócić dowolny punkt na płaszczyźnie; na przykład punkt (2,0) = 2 + 0 × f zmienia się na (1, -1), a następnie na (-1, -1), (-2,0), (-1,1), ( 1,1) i wreszcie wróć do (2,0), po prostu mnożąc go.
Oczywiście potrzebujemy sposobu na przeniesienie tych punktów ze współrzędnych na te, w których wykonujemy obrót, a następnie z powrotem. W tym celu potrzebna jest jeszcze jedna informacja: jeśli punkt, który wykonujemy, obraca się wokół „lewej” lub „prawej” linii pionowej. Dla uproszczenia deklarujemy, że ma on wartość „wahania” w 0, jeśli znajduje się po jego lewej stronie (jak środek obrotu [0,0] na dwóch dolnych obrazach), i 1, jeśli znajduje się po prawej stronie z tego. To rozszerza nasze pierwotne punkty na trójwymiarowe; ( x , y , w ), przy czym „w” wynosi 0 lub 1 po normalizacji. Funkcja normalizacji to:
NORM: ( x , y , w ) -> ( x + floor ( w / 2), y , w mod 2), przy zdefiniowanej operacji „mod” tak, że zwraca tylko wartości dodatnie lub zero.
Nasz algorytm wygląda teraz następująco:
Przekształć nasze punkty ( a , b , c ) w ich pozycje względem środka obrotu ( x , y , w ), obliczając ( a - x , b - y , c - w ), a następnie normalizując wynik. To oczywiście ustawia środek obrotu na (0,0,0).
Przekształć nasze punkty z ich „rodzimych” współrzędnych na obrotowe zespoły złożone: ( a , b , c ) -> (2 × a + c , b ) = 2 × a + c + b × f
Obróć nasze punkty, mnożąc je w razie potrzeby przez jedną z powyższych liczb obrotowych.
Ra przekształca punkty z powrotem ze współrzędnych obrotowych na ich „rodzime”: ( r , s ) -> (floor ( r / 2), s , r mod 2), z „mod” zdefiniowanym jak powyżej.
Ponownie przekształć punkty z powrotem do ich pierwotnej pozycji, dodając je do środka obrotu ( x , y , z ) i normalizując.
Prosta wersja z naszych numerów „potrójnych” oparty f w C ++ będzie wyglądać następująco:
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));
}
};