/***************************************************************************
*                                                                          *
*  SKEWB   by Raymond S. Brand                                             *
*                                                                          *
***************************************************************************/

/***************************************************************************
*                                                                          *
*  The weird looking constants are because this program is actually a      *
*  'C' version written for the Amiga of the original Pascal program that   *
*  was written for a Tektronics 4113 Color Raster Display Terminal.        *
*                                                                          *
*                                         RSBX                             *
*                                                                          *
***************************************************************************/

#include <exec/types.h>
#include <intuition/intuition.h>
#include <graphics/gfxmacros.h>

#define MAXMODE   1                   /* Highres-Interlace ( 640 x 400 ) */
#define TALLMODE  2                   /* Interlace         ( 320 x 400 ) */
#define WIDEMODE  3                   /* Highres           ( 640 x 200 ) */
#define MINMODE   4                   /*                   ( 320 x 200 ) */

#define DEFAULTMODE  MAXMODE

#ifndef DISPLAYMODE
#define DISPLAYMODE DEFAULTMODE
#endif

#if (DISPLAYMODE == MAXMODE)
#define  DISPLAYWIDTH      640
#define  DISPLAYHEIGHT     400
#define  DISPLAYVIEWMODE   HIRES | LACE
#endif

#if (DISPLAYMODE == TALLMODE)
#define  DISPLAYWIDTH      320
#define  DISPLAYHEIGHT     400
#define  DISPLAYVIEWMODE   LACE
#endif

#if (DISPLAYMODE == WIDEMODE)
#define  DISPLAYWIDTH      640
#define  DISPLAYHEIGHT     200
#define  DISPLAYVIEWMODE   HIRES
#endif

#if (DISPLAYMODE == MINMODE)
#define  DISPLAYWIDTH      320
#define  DISPLAYHEIGHT     200
#define  DISPLAYVIEWMODE   NULL
#endif

struct IntuitionBase *IntuitionBase;
struct GfxBase *GfxBase;

#define INTUITION_REV 29
#define GRAPHICS_REV  29

struct TextAttr MyFont =
  {
   "topaz.font",              /* Font Name        */
   TOPAZ_SIXTY,               /* Font Height      */
   FS_NORMAL,                 /* Style            */
   FPF_ROMFONT,               /* Preferences      */
  };

struct NewScreen NewScreen =
  {
   0,                         /* LeftEdge                             */
   0,                         /* TopEdge                              */
   DISPLAYWIDTH,              /* Width                                */
   DISPLAYHEIGHT,             /* Height                               */
   4,                         /* Depth (4 planes = 16 colors)         */
   1,                         /* DetailPen                            */
   2,                         /* BlockPen                             */
   DISPLAYVIEWMODE,           /* ViewModes                            */
   CUSTOMSCREEN,              /* Type                                 */
   &MyFont,                   /* *Font                                */
   "Skewb   by Raymond S. Brand", /* *DefaultTitle                    */
   NULL,                      /* *Gadgets                             */
   NULL,                      /* *CustomBitMap                        */
  };

struct Screen *Screen;
struct NewWindow NewWindow;
struct Window *Window1;
struct Window *Window2;

struct Verticies
  {
   SHORT X;
   SHORT Y;
  };

struct TileRecord
  {
   SHORT Color;
   struct Verticies TileVert[4];
  };

struct TileRecord Tiles[6][11][11];

WORD  areaverts[25];
struct TmpRas arearas;
struct AreaInfo areadata;

VOID  OpenDisplay();
VOID  CloseDisplay();
VOID  SetColors();
SHORT Order();
SHORT Sign();
SHORT CubeCorr_to_ArrayCorr();
SHORT ArrayCorr_to_CubeCorr();
SHORT Tile();
VOID  ChangeDisplay();
VOID  CreateCube();
VOID  RotateSlice();
VOID  MoveSlice();
VOID  ChangeTile();
VOID  InitSkewb();
SHORT Norm_to_Axis();
SHORT Norm_to_Face();
VOID  Swap();
VOID  RotateVector();
VOID  RotatePlane();
VOID  RotateFace();
VOID  RotateSlice();

SHORT Cube_Order;

#define  Cos1      0.7071067811865
#define  Sin1     -0.7071067811865
#define  Cos2      0.8164965809282
#define  Sin2      0.57735026919
#define  Distance -0.125

DOUBLE   M1X =     ( (  700.0 / 4096.0 ) * DISPLAYWIDTH );
DOUBLE   M1Y =     ( (  700.0 / 3048.0 ) * DISPLAYHEIGHT );
DOUBLE   M2X =    -( (  200.0 / 4096.0 ) * DISPLAYWIDTH );
DOUBLE   M2Y =    -( (  200.0 / 3048.0 ) * DISPLAYHEIGHT );

SHORT    X1 =      ( ( 1648.0 / 4096.0 ) * DISPLAYWIDTH );
SHORT    Y1 =      ( ( 1606.0 / 3048.0 ) * DISPLAYHEIGHT );
SHORT    Z1 =      1;
SHORT    X2 =      ( (  462.0 / 4096.0 ) * DISPLAYWIDTH );
SHORT    Y2 =      ( ( 2618.0 / 3048.0 ) * DISPLAYHEIGHT );
SHORT    Z2 =     -1;
SHORT    X3 =      ( ( 2836.0 / 4096.0 ) * DISPLAYWIDTH );
SHORT    Y3 =      ( ( 2608.0 / 3048.0 ) * DISPLAYHEIGHT );
SHORT    Z3 =      1;

main ()
  {
   struct IntuiMessage *message;
   ULONG class;
   USHORT code;
   SHORT GoodBye;
   SHORT Axis;
   SHORT Plane;

   OpenDisplay();

   InitSkewb();

   GoodBye = FALSE;
   Axis = 1;
   Plane = 0;

   FOREVER
     {
      while (message = (struct IntuiMessage *)GetMsg(Window2->UserPort))
        {
         class = message->Class;
         code = message->Code;
         ReplyMsg(message);

         if(class == CLOSEWINDOW)
           {
            GoodBye = TRUE;
            break;
           };
         if(class == MOUSEBUTTONS)
           {
            if(code == SELECTDOWN)
              {
               Cube_Order = (Cube_Order - 1) % 9 + 2;
               CreateCube();
               Plane = 0;
               Axis = 1;
              };
           };
        };
      if(GoodBye) break;

      Delay(25);
      RotateSlice(Axis,Plane,1);
      Axis += Sign(Axis);
      if(Axis > 3)
         Axis = -1;
      if(Axis < -3)
        {
         Axis = 1;
         Plane = (Plane + 1) % Cube_Order;
        };
     };

   CloseDisplay();

   exit(TRUE);
  }



VOID  OpenDisplay()
  {
   IntuitionBase = (struct IntuitionBase *)
      OpenLibrary("intuition.library",INTUITION_REV);
   if (IntuitionBase == NULL) exit(20);

   GfxBase = (struct GfxBase *)OpenLibrary("graphics.library",GRAPHICS_REV);
   if (GfxBase == NULL) exit(20);

   if ((Screen = (struct Screen *)OpenScreen(&NewScreen)) == NULL)
      exit(20);

   ShowTitle(Screen,FALSE);  /* Put the Screen title behind any
                                 backdrop window that may exist   */

   /* Define Vanilla backdrop window for drawing onto */
   NewWindow.LeftEdge    = 0;
   NewWindow.TopEdge     = 0;
   NewWindow.Width       = DISPLAYWIDTH;
   NewWindow.Height      = DISPLAYHEIGHT;
   NewWindow.DetailPen   = 1;
   NewWindow.BlockPen    = 2;
   NewWindow.IDCMPFlags  = NULL;
   NewWindow.Flags       = SMART_REFRESH | BACKDROP | ACTIVATE;
   NewWindow.FirstGadget = NULL;
   NewWindow.CheckMark   = NULL;
   NewWindow.Title       = NULL;
   NewWindow.Screen      = Screen;
   NewWindow.BitMap      = NULL;
   NewWindow.MinWidth    = DISPLAYWIDTH;
   NewWindow.MinHeight   = DISPLAYHEIGHT;
   NewWindow.MaxWidth    = DISPLAYWIDTH;
   NewWindow.MaxHeight   = DISPLAYHEIGHT;
   NewWindow.Type        = CUSTOMSCREEN;

   if ((Window1 = (struct Window *)OpenWindow(&NewWindow)) == NULL)
     {
      CloseScreen(Screen);
      exit(20);
     };

   /* Define a window with a close gadget so that we can exit this program */
   NewWindow.LeftEdge    = DISPLAYWIDTH - 100;
   NewWindow.TopEdge     = DISPLAYHEIGHT - 50;
   NewWindow.Width       = 100;
   NewWindow.Height      = 50;
   NewWindow.DetailPen   = 1;
   NewWindow.BlockPen    = 2;
   NewWindow.IDCMPFlags  = CLOSEWINDOW | MOUSEBUTTONS;
   NewWindow.Flags       = SMART_REFRESH | WINDOWDRAG | WINDOWCLOSE;
   NewWindow.FirstGadget = NULL;
   NewWindow.CheckMark   = NULL;
   NewWindow.Title       = "Skewb";
   NewWindow.Screen      = Screen;
   NewWindow.BitMap      = NULL;
   NewWindow.MinWidth    = 100;
   NewWindow.MinHeight   = 50;
   NewWindow.MaxWidth    = 100;
   NewWindow.MaxHeight   = 50;
   NewWindow.Type        = CUSTOMSCREEN;

   if ((Window2 = (struct Window *)OpenWindow(&NewWindow)) == NULL)
     {
      CloseWindow(Window1);
      CloseScreen(Screen);
      exit(20);
     };

   SetColors();

   InitArea(&areadata,areaverts,10);
   Window1->RPort->AreaInfo=&areadata;

   if((arearas.RasPtr = (BYTE *)AllocRaster(DISPLAYWIDTH,DISPLAYHEIGHT)) == NULL)
     {
      CloseWindow(Window2);
      CloseWindow(Window1);
      CloseScreen(Screen);
      exit(20);
     };

   arearas.Size=RASSIZE(DISPLAYWIDTH,DISPLAYHEIGHT);
   Window1->RPort->TmpRas=&arearas;

   SetRast(Window1->RPort,0);

   SetOPen(Window1->RPort,15);
  }



VOID  CloseDisplay()
  {
   FreeRaster(arearas.RasPtr,DISPLAYWIDTH,DISPLAYHEIGHT);

   CloseWindow(Window1);
   CloseWindow(Window2);

   CloseScreen(Screen);
  }



VOID  SetColors()
  {
   SetRGB4(&Screen->ViewPort, 0, 8, 8, 8);      /* background */
   SetRGB4(&Screen->ViewPort, 1,15,15,15);      /* detail     */
   SetRGB4(&Screen->ViewPort, 2, 0, 0, 0);      /* block      */
   SetRGB4(&Screen->ViewPort, 3, 6, 6, 6);
   SetRGB4(&Screen->ViewPort, 4, 6, 6, 6);
   SetRGB4(&Screen->ViewPort, 5, 6, 6, 6);
   SetRGB4(&Screen->ViewPort, 6, 6, 6, 6);
   SetRGB4(&Screen->ViewPort, 7, 6, 6, 6);
   SetRGB4(&Screen->ViewPort, 8,15, 0, 0);      /* red        */
   SetRGB4(&Screen->ViewPort, 9, 0,15, 0);      /* green      */
   SetRGB4(&Screen->ViewPort,10, 0, 0,15);      /* blue       */
   SetRGB4(&Screen->ViewPort,11,15,15,15);      /* white      */
   SetRGB4(&Screen->ViewPort,12,15, 9, 0);      /* orange     */
   SetRGB4(&Screen->ViewPort,13,15,15, 0);      /* yellow     */
   SetRGB4(&Screen->ViewPort,14, 6, 6, 6);
   SetRGB4(&Screen->ViewPort,15, 0, 0, 0);      /* outline    */
  }



SHORT Order()
  {
   return(Cube_Order);
  }



SHORT Sign(number)
   SHORT number;
  {
   if (number == 0) return(0);
   if (number > 0) return(1);
   return(-1);
  }



SHORT CubeCorr_to_ArrayCorr(Cube_Corr)
   SHORT Cube_Corr;
  {
   if ((Cube_Order % 2) == 0) /* even */

      return( (SHORT)( Cube_Order / 2 - Cube_Corr
             - ( Cube_Order + 2 - 2 * Cube_Corr ) / ( Cube_Order + 2 ) ) );

   else /* odd */

      return( (SHORT)( ( Cube_Order - 1 - 2 * Cube_Corr ) / 2 ) );
  }



SHORT ArrayCorr_to_CubeCorr(Array_Corr)
   SHORT Array_Corr;
  {
   if ((Cube_Order % 2) == 0) /* even */

      return( (SHORT)( Cube_Order / 2 - Array_Corr
                       - ( 2 * Array_Corr + 2 ) / ( Cube_Order + 1 ) ) );

   else /* odd */

      return( (SHORT)( ( Cube_Order - 1 - 2 * Array_Corr ) / 2 ) );
  }



SHORT Tile()
  {
  }



VOID  ChangeTile(tileptr,x,y,z)
   struct TileRecord *tileptr;
   SHORT x;
   SHORT y;
   SHORT z;
  {
   SHORT i;

   SetAPen(Window1->RPort,tileptr->Color+8);

   AreaMove(Window1->RPort,tileptr->TileVert[0].X*z+x,
                           DISPLAYHEIGHT - (tileptr->TileVert[0].Y*z+y));

   for (i = 1; i < 4; i++)
      AreaDraw(Window1->RPort,tileptr->TileVert[i].X*z+x,
                              DISPLAYHEIGHT - (tileptr->TileVert[i].Y*z+y));

   AreaEnd(Window1->RPort);
  }



VOID  ChangeDisplay(face,x,y)
   SHORT face;
   SHORT x;
   SHORT y;
  {
   struct TileRecord *TileData;

   TileData = &Tiles[face][x][y];   /* should point to the tile structure */

   if (face < 3)

      ChangeTile(TileData,X1,Y1,Z1);

   else

     {
      ChangeTile(TileData,X2,Y2,Z2);
      ChangeTile(TileData,X3,Y3,Z3);
     };
  }



VOID  CreateCube()
  {
   SHORT Direction;
   SHORT Plane;
   SHORT Index1;
   SHORT Index2;
   SHORT Index3;
   SHORT Index4;
   DOUBLE Temp;
   struct {
           DOUBLE X;
           DOUBLE Y;
          } Matrix[11][11];
   struct TileRecord *withtile;
   DOUBLE Vector[3];
   DOUBLE MultX;
   DOUBLE MultY;

   MultX = M1X;
   MultY = M1Y;
   for(Direction=0;Direction<2;Direction++)
     {
      for(Plane=0;Plane<3;Plane++)
        {
         for(Index1=0;Index1<=Cube_Order;Index1++)
           {
            for(Index2=0;Index2<=Cube_Order;Index2++)
              {
               Vector[Plane]=(1.0 - 2.0 * Direction);
               Vector[(Plane+1) % 3]=((Cube_Order - 2.0 * Index1) / Cube_Order);
               Vector[(Plane+2) % 3]=((Cube_Order - 2.0 * Index2) / Cube_Order);
               Temp=(1.0 + Distance * (1.0 - 2.0 * Direction)
                     * ( -Vector[0] * Sin1 * Cos2
                         +Vector[1] * Sin2
                         +Vector[2] * Cos1 * Cos2 ));
               Matrix[Index1][Index2].X=( Vector[0] * Cos1
                                         +Vector[2] * Sin1 ) / Temp;
               Matrix[Index1][Index2].Y=( Vector[0] * Sin1 * Sin2
                                         +Vector[1] * Cos2
                                         -Vector[2] * Cos1 * Sin2 ) / Temp;
              }
           };
         for(Index1=0;Index1<Cube_Order;Index1++)
           {
            for(Index2=0;Index2<Cube_Order;Index2++)
              {
               withtile = &Tiles[Plane + 3 * Direction][Index1][Index2];
               withtile->Color=Plane + 3 * Direction;

               for(Index3=0;Index3<2;Index3++)
                 {
                  for(Index4=0;Index4<2;Index4++)
                    {
                     withtile->TileVert[Index3 * 2 + Index4].X
                           = (Matrix[Index1 + Index3]
                                   [Index2 + Index3 + Index4
                                   -2 * Index3 * Index4].X
                                   * (1.0 - 2.0 * Direction) * MultX);
                     withtile->TileVert[Index3 * 2 + Index4].Y
                           = (Matrix[Index1 + Index3]
                                   [Index2 + Index3 + Index4
                                   -2 * Index3 * Index4].Y * MultY);
                    };
                 };
               ChangeDisplay(Plane + 3 * Direction,Index1,Index2);
              };
           };
        };
      MultX = M2X;
      MultY = M2Y;
     };
  }



VOID  InitSkewb()
  {
   Cube_Order = 2;
   CreateCube();
  }



SHORT Norm_to_Axis(Vector)
   SHORT *Vector;
  {
   SHORT Temp;
   SHORT Index;

   Temp = 0;
   for(Index=0 ; Index<3 ; Index++)
      Temp += Sign(Vector[Index]) * (Index + 1);
   return(Temp);
  }



SHORT Norm_to_Face(Vector)
   SHORT *Vector;
  {
   SHORT Temp;

   Temp = Norm_to_Axis(Vector);
   if(Temp<0)
      Temp = 3 - Temp;
   Temp -= 1;
   return(Temp);
  }


VOID  Swap_Short(a,b)
   SHORT *a;
   SHORT *b;
  {
   SHORT Temp;

   Temp = *a;
   *a = *b;
   *b = Temp;
  }




VOID  RotateVector(Vector,Axis,Direction)
   SHORT *Vector;
   SHORT Axis;
   SHORT Direction;
  {
   SHORT Rotate;

   Rotate = Sign(Axis) * Sign(Direction);
   Axis = Sign(Axis) * Axis;
   Swap_Short(&Vector[Axis % 3],&Vector[(Axis + 1) % 3]);

   if(Rotate>0)
      Vector[(Axis + 1) % 3] *= -1;
   else
      Vector[Axis % 3] *= -1;
  }



VOID  RotatePlane(Axis,Plane,Direction)
   SHORT Axis;
   SHORT Plane;
   SHORT Direction;
  {
   SHORT Norm[3];
   SHORT Tile[3];
   SHORT n;
   SHORT x;
   SHORT y;
   SHORT z;
   SHORT Color;
   SHORT Index1;
   SHORT Index2;

   x = Sign(Axis) * Axis - 1;
   y = (x + 1) % 3;
   z = (y + 1) % 3;
   Norm[x] = 0;
   Norm[y] = ArrayCorr_to_CubeCorr(0);
   Norm[z] = 0;

   Tile[x] = Sign(Axis) * ArrayCorr_to_CubeCorr(Plane);

   for(Index1=0 ; Index1<Cube_Order ; Index1++)
     {
      x = Norm_to_Axis(&Norm);
      x = Sign(x) * x - 1;
      y = (x + 1) % 3;
      z = (y + 1) % 3;
      Tile[x] = Norm[x];
      Tile[y] = ArrayCorr_to_CubeCorr(Index1);
      Color = Tiles[Norm_to_Face(&Norm)]
                   [CubeCorr_to_ArrayCorr(Tile[y])]
                   [CubeCorr_to_ArrayCorr(Tile[z])].Color;
      for(Index2=0 ; Index2<4 ; Index2++)
        {
         RotateVector(&Norm,Axis,Direction);
         RotateVector(&Tile,Axis,Direction);
         x = Norm_to_Axis(&Norm);
         x = Sign(x) * x - 1;
         y = CubeCorr_to_ArrayCorr(Tile[(x + 1) % 3]);
         z = CubeCorr_to_ArrayCorr(Tile[(x + 2) % 3]);
         n = Norm_to_Face(&Norm);
         Swap_Short(&Color,&Tiles[n][y][z].Color);
         ChangeDisplay(n,y,z);
        };
     };
  }



VOID  RotateFace(Axis,Plane,Direction)
   SHORT Axis;
   SHORT Plane;
   SHORT Direction;
  {
   SHORT Tile[3];
   SHORT Color;
   SHORT n;
   SHORT x;
   SHORT y;
   SHORT z;
   SHORT Index1;
   SHORT Index2;
   SHORT Index3;

   x = Sign(Axis) * Axis - 1;
   y = (x + 1) % 3;
   z = (y + 1) % 3;
   n = Axis * Sign(ArrayCorr_to_CubeCorr(Plane));
   if(n<0)
      n = 3 - n - 1;
   else
      n -= 1;
   for(Index1=0 ; Index1 < (Cube_Order / 2) ; Index1++)
     {
      for(Index2=0 ; Index2 <= (Cube_Order - 2 * (Index1 + 1)) ; Index2++)
        {
         Tile[y] = ArrayCorr_to_CubeCorr(Index1);
         Tile[z] = ArrayCorr_to_CubeCorr(Index1 + Index2);
         Color = Tiles[n][Index1][Index1 + Index2].Color;
         for(Index3=0 ; Index3<4 ; Index3++)
           {
            RotateVector(&Tile,Axis,Direction);
            Swap_Short(&Color,&Tiles[n]
                                    [CubeCorr_to_ArrayCorr(Tile[y])]
                                    [CubeCorr_to_ArrayCorr(Tile[z])].Color);
            ChangeDisplay(n,CubeCorr_to_ArrayCorr(Tile[y])
                           ,CubeCorr_to_ArrayCorr(Tile[z]));
           };
        };
     };
  }


VOID  RotateSlice(Axis,Plane,Displacement)
   SHORT Axis;
   SHORT Plane;
   SHORT Displacement;
  {
   for( ; Displacement != 0 ; Displacement -= Sign(Displacement))
     {
      RotatePlane(Axis,Plane,Displacement);
      if( (Plane * (Cube_Order - Plane - 1)) == 0 )
         RotateFace(Axis,Plane,Displacement);
     };
  }

