29    private function __construct(){
 
   33    public const FLAG_AXIS_POSITIVE = 1;
 
   36    public const DOWN =   Axis::Y << 1;
 
   37    public const UP =    (Axis::Y << 1) | self::FLAG_AXIS_POSITIVE;
 
   38    public const NORTH =  Axis::Z << 1;
 
   39    public const SOUTH = (Axis::Z << 1) | self::FLAG_AXIS_POSITIVE;
 
   40    public const WEST =   Axis::X << 1;
 
   41    public const EAST =  (Axis::X << 1) | self::FLAG_AXIS_POSITIVE;
 
   52    public const HORIZONTAL = [
 
   59    public const OFFSET = [
 
   60        self::DOWN  => [ 0, -1,  0],
 
   61        self::UP    => [ 0, +1,  0],
 
   62        self::NORTH => [ 0,  0, -1],
 
   63        self::SOUTH => [ 0,  0, +1],
 
   64        self::WEST  => [-1,  0,  0],
 
   65        self::EAST  => [+1,  0,  0]
 
   68    private const CLOCKWISE = [
 
   70            self::NORTH => self::EAST,
 
   71            self::EAST => self::SOUTH,
 
   72            self::SOUTH => self::WEST,
 
   73            self::WEST => self::NORTH
 
   76            self::UP => self::EAST,
 
   77            self::EAST => self::DOWN,
 
   78            self::DOWN => self::WEST,
 
   79            self::WEST => self::UP
 
   82            self::UP => self::NORTH,
 
   83            self::NORTH => self::DOWN,
 
   84            self::DOWN => self::SOUTH,
 
   85            self::SOUTH => self::UP
 
   92    public static function axis(
int $direction) : int{
 
   93        return $direction >> 1; 
 
 
   99    public static function isPositive(
int $direction) : bool{
 
  100        return ($direction & self::FLAG_AXIS_POSITIVE) === self::FLAG_AXIS_POSITIVE;
 
 
  108    public static function opposite(
int $direction) : int{
 
  109        return $direction ^ self::FLAG_AXIS_POSITIVE;
 
 
  117    public static function rotate(
int $direction, 
int $axis, 
bool $clockwise) : int{
 
  118        if(!isset(self::CLOCKWISE[$axis])){
 
  119            throw new \InvalidArgumentException(
"Invalid axis $axis");
 
  121        if(!isset(self::CLOCKWISE[$axis][$direction])){
 
  122            throw new \InvalidArgumentException(
"Cannot rotate facing \"" . self::toString($direction) . 
"\" around axis \"" . Axis::toString($axis) . 
"\"");
 
  125        $rotated = self::CLOCKWISE[$axis][$direction];
 
  126        return $clockwise ? $rotated : self::opposite($rotated);
 
 
  132    public static function rotateY(
int $direction, 
bool $clockwise) : int{
 
  133        return self::rotate($direction, 
Axis::Y, $clockwise);
 
 
  139    public static function rotateZ(
int $direction, 
bool $clockwise) : int{
 
  140        return self::rotate($direction, 
Axis::Z, $clockwise);
 
 
  146    public static function rotateX(
int $direction, 
bool $clockwise) : int{
 
  147        return self::rotate($direction, 
Axis::X, $clockwise);
 
 
  155    public static function validate(
int $facing) : void{
 
  156        if(!in_array($facing, self::ALL, true)){
 
  157            throw new \InvalidArgumentException(
"Invalid direction $facing");
 
 
  164    public static function toString(
int $facing) : string{
 
  165        return match($facing){
 
  166            self::DOWN => 
"down",
 
  168            self::NORTH => 
"north",
 
  169            self::SOUTH => 
"south",
 
  170            self::WEST => 
"west",
 
  171            self::EAST => 
"east",
 
  172            default => 
throw new \InvalidArgumentException(
"Invalid facing $facing")