/*
 * @(#)cube3D.c
 *
 * Copyright 1999-2000, Aaron Ardiri (mailto:aaron@ardiri.com)
 * All rights reserved.
 *
 * The  source code  outlines a number of basic Palm Computing Programming
 * principles and you  should be able to take the core structure and write 
 * a large complex program. It is distributed WITHOUT ANY WARRANTY; use it
 * "AS IS" and at your own risk.
 *
 * The code presented is Copyright 1999-2000 by Aaron Ardiri. It should be
 * used for  educational purposes only.  You  shall not modify  the Cube3D 
 * source code in any way and  re-distribute it as your  own,  however you
 * are free to use  the code as  a guide for  developing  programs  on the 
 * Palm Computing Platform.
 */

#include "palm.h"

// global variable structure
typedef struct
{
  Coord vertex[8][3];
  Int16 dx, dy, dz;
} CubeGlobals;

// interface
static void CubePerspectiveConversion(Coord, Coord, Coord, Coord *, Coord *);
static void CubeRotationAboutX(Coord, Coord, Coord, 
                               Coord *, Coord *, Coord *, Int16);
static void CubeRotationAboutY(Coord, Coord, Coord, 
                               Coord *, Coord *, Coord *, Int16);
static void CubeRotationAboutZ(Coord, Coord, Coord, 
                               Coord *, Coord *, Coord *, Int16);

/**
 * sin(x) table for 0 <= x < 360
 * - values are generated using a 32 bit "fixed" type datatype
 */
static fixed
sin_table[] = 
{
0x00000000, 0x00000004, 0x00000008, 0x0000000d, 
0x00000011, 0x00000016, 0x0000001a, 0x0000001f, 
0x00000023, 0x00000028, 0x0000002c, 0x00000030, 
0x00000035, 0x00000039, 0x0000003d, 0x00000042, 
0x00000046, 0x0000004a, 0x0000004f, 0x00000053, 
0x00000057, 0x0000005b, 0x0000005f, 0x00000064, 
0x00000068, 0x0000006c, 0x00000070, 0x00000074, 
0x00000078, 0x0000007c, 0x00000080, 0x00000083, 
0x00000087, 0x0000008b, 0x0000008f, 0x00000092, 
0x00000096, 0x0000009a, 0x0000009d, 0x000000a1, 
0x000000a4, 0x000000a7, 0x000000ab, 0x000000ae, 
0x000000b1, 0x000000b5, 0x000000b8, 0x000000bb, 
0x000000be, 0x000000c1, 0x000000c4, 0x000000c6, 
0x000000c9, 0x000000cc, 0x000000cf, 0x000000d1, 
0x000000d4, 0x000000d6, 0x000000d9, 0x000000db, 
0x000000dd, 0x000000df, 0x000000e2, 0x000000e4, 
0x000000e6, 0x000000e8, 0x000000e9, 0x000000eb, 
0x000000ed, 0x000000ee, 0x000000f0, 0x000000f2, 
0x000000f3, 0x000000f4, 0x000000f6, 0x000000f7, 
0x000000f8, 0x000000f9, 0x000000fa, 0x000000fb, 
0x000000fc, 0x000000fc, 0x000000fd, 0x000000fe, 
0x000000fe, 0x000000ff, 0x000000ff, 0x000000ff, 
0x000000ff, 0x000000ff, 0x00000100, 0x000000ff, 
0x000000ff, 0x000000ff, 0x000000ff, 0x000000ff, 
0x000000fe, 0x000000fe, 0x000000fd, 0x000000fc, 
0x000000fc, 0x000000fb, 0x000000fa, 0x000000f9, 
0x000000f8, 0x000000f7, 0x000000f6, 0x000000f4, 
0x000000f3, 0x000000f2, 0x000000f0, 0x000000ee, 
0x000000ed, 0x000000eb, 0x000000e9, 0x000000e8, 
0x000000e6, 0x000000e4, 0x000000e2, 0x000000df, 
0x000000dd, 0x000000db, 0x000000d9, 0x000000d6, 
0x000000d4, 0x000000d1, 0x000000cf, 0x000000cc, 
0x000000c9, 0x000000c6, 0x000000c4, 0x000000c1, 
0x000000be, 0x000000bb, 0x000000b8, 0x000000b5, 
0x000000b1, 0x000000ae, 0x000000ab, 0x000000a7, 
0x000000a4, 0x000000a1, 0x0000009d, 0x0000009a, 
0x00000096, 0x00000092, 0x0000008f, 0x0000008b, 
0x00000087, 0x00000083, 0x0000007f, 0x0000007c, 
0x00000078, 0x00000074, 0x00000070, 0x0000006c, 
0x00000068, 0x00000064, 0x0000005f, 0x0000005b, 
0x00000057, 0x00000053, 0x0000004f, 0x0000004a, 
0x00000046, 0x00000042, 0x0000003d, 0x00000039, 
0x00000035, 0x00000030, 0x0000002c, 0x00000028, 
0x00000023, 0x0000001f, 0x0000001a, 0x00000016, 
0x00000011, 0x0000000d, 0x00000008, 0x00000004, 
0x00000000, 0xfffffffc, 0xfffffff8, 0xfffffff3, 
0xffffffef, 0xffffffea, 0xffffffe6, 0xffffffe1, 
0xffffffdd, 0xffffffd8, 0xffffffd4, 0xffffffd0, 
0xffffffcb, 0xffffffc7, 0xffffffc3, 0xffffffbe, 
0xffffffba, 0xffffffb6, 0xffffffb1, 0xffffffad, 
0xffffffa9, 0xffffffa5, 0xffffffa1, 0xffffff9c, 
0xffffff98, 0xffffff94, 0xffffff90, 0xffffff8c, 
0xffffff88, 0xffffff84, 0xffffff80, 0xffffff7d, 
0xffffff79, 0xffffff75, 0xffffff71, 0xffffff6e, 
0xffffff6a, 0xffffff66, 0xffffff63, 0xffffff5f, 
0xffffff5c, 0xffffff59, 0xffffff55, 0xffffff52, 
0xffffff4f, 0xffffff4b, 0xffffff48, 0xffffff45, 
0xffffff42, 0xffffff3f, 0xffffff3c, 0xffffff3a, 
0xffffff37, 0xffffff34, 0xffffff31, 0xffffff2f, 
0xffffff2c, 0xffffff2a, 0xffffff27, 0xffffff25, 
0xffffff23, 0xffffff21, 0xffffff1e, 0xffffff1c, 
0xffffff1a, 0xffffff18, 0xffffff17, 0xffffff15, 
0xffffff13, 0xffffff12, 0xffffff10, 0xffffff0e, 
0xffffff0d, 0xffffff0c, 0xffffff0a, 0xffffff09, 
0xffffff08, 0xffffff07, 0xffffff06, 0xffffff05, 
0xffffff04, 0xffffff04, 0xffffff03, 0xffffff02, 
0xffffff02, 0xffffff01, 0xffffff01, 0xffffff01, 
0xffffff01, 0xffffff01, 0xffffff00, 0xffffff01, 
0xffffff01, 0xffffff01, 0xffffff01, 0xffffff01, 
0xffffff02, 0xffffff02, 0xffffff03, 0xffffff04, 
0xffffff04, 0xffffff05, 0xffffff06, 0xffffff07, 
0xffffff08, 0xffffff09, 0xffffff0a, 0xffffff0c, 
0xffffff0d, 0xffffff0e, 0xffffff10, 0xffffff12, 
0xffffff13, 0xffffff15, 0xffffff17, 0xffffff18, 
0xffffff1a, 0xffffff1c, 0xffffff1e, 0xffffff21, 
0xffffff23, 0xffffff25, 0xffffff27, 0xffffff2a, 
0xffffff2c, 0xffffff2f, 0xffffff31, 0xffffff34, 
0xffffff37, 0xffffff3a, 0xffffff3c, 0xffffff3f, 
0xffffff42, 0xffffff45, 0xffffff48, 0xffffff4b, 
0xffffff4f, 0xffffff52, 0xffffff55, 0xffffff59, 
0xffffff5c, 0xffffff5f, 0xffffff63, 0xffffff66, 
0xffffff6a, 0xffffff6e, 0xffffff71, 0xffffff75, 
0xffffff79, 0xffffff7d, 0xffffff81, 0xffffff84, 
0xffffff88, 0xffffff8c, 0xffffff90, 0xffffff94, 
0xffffff98, 0xffffff9c, 0xffffffa1, 0xffffffa5, 
0xffffffa9, 0xffffffad, 0xffffffb1, 0xffffffb6, 
0xffffffba, 0xffffffbe, 0xffffffc3, 0xffffffc7, 
0xffffffcb, 0xffffffd0, 0xffffffd4, 0xffffffd8, 
0xffffffdd, 0xffffffe1, 0xffffffe6, 0xffffffea, 
0xffffffef, 0xfffffff3, 0xfffffff8, 0xfffffffc, 
};
#define ABS(a) (((a) > 0) ? (a) : -(a))
static fixed cos_fx(Int16 deg) { return sin_table[ABS((deg+90)%360)]; }
static fixed sin_fx(Int16 deg) { return sin_table[ABS(deg%360)]; }

/**
 * Initialize the Cube3D engine.
 *
 * @param size the size of the cube.
 */  
void   
CubeInitialize(Coord size)
{
  CubeGlobals *globals;

  // create the globals object, and register it
  globals = (CubeGlobals *)MemPtrNew(sizeof(CubeGlobals));
  MemSet(globals, sizeof(CubeGlobals), 0);
  FtrSet(appCreator, ftrCubeGlobals, (UInt32)globals);

  // setup our cube (hardcoded sizes here :P)
  globals->vertex[0][0] = -size;
  globals->vertex[0][1] =  size;
  globals->vertex[0][2] = -size;
  globals->vertex[1][0] = -size;
  globals->vertex[1][1] =  size;
  globals->vertex[1][2] =  size;
  globals->vertex[2][0] =  size;
  globals->vertex[2][1] =  size;
  globals->vertex[2][2] =  size;
  globals->vertex[3][0] =  size;
  globals->vertex[3][1] =  size;
  globals->vertex[3][2] = -size;
  globals->vertex[4][0] = -size;
  globals->vertex[4][1] = -size;
  globals->vertex[4][2] = -size;
  globals->vertex[5][0] = -size;
  globals->vertex[5][1] = -size;
  globals->vertex[5][2] =  size;
  globals->vertex[6][0] =  size;
  globals->vertex[6][1] = -size;
  globals->vertex[6][2] =  size;
  globals->vertex[7][0] =  size;
  globals->vertex[7][1] = -size;
  globals->vertex[7][2] = -size;

  // reset the "angle" parameters
  globals->dx = globals->dy = globals->dz = 0;
}

/**
 * Animate the Cube3D engine.
 */
void
CubeAnimate()
{
  CubeGlobals *globals;
  Coord       vertex[8][3];
  Coord       points[8][2];
  UInt16      i;

  // get a globals reference
  FtrGet(appCreator, ftrCubeGlobals, (UInt32 *)&globals);

  // perform the "rotation" of the cube
  for (i=0; i<8; i++) {
    CubeRotationAboutX(
      globals->vertex[i][0], globals->vertex[i][1], globals->vertex[i][2], 
      &vertex[i][0],         &vertex[i][1],         &vertex[i][2],
      globals->dx
    );
    CubeRotationAboutY(
      vertex[i][0],          vertex[i][1],          vertex[i][2], 
      &vertex[i][0],         &vertex[i][1],         &vertex[i][2],
      globals->dy
    );
    CubeRotationAboutZ(
      vertex[i][0],          vertex[i][1],          vertex[i][2], 
      &vertex[i][0],         &vertex[i][1],         &vertex[i][2],
      globals->dz
    );
  }

  // adjust the rotation angles
  globals->dx += 2;
  globals->dy += 1;
  globals->dz += 3;

  // convert the cube from 3D to 2D using perspective geometry
  for (i=0; i<8; i++) {

    CubePerspectiveConversion(vertex[i][0],  vertex[i][1],  vertex[i][2],
                              &points[i][0], &points[i][1]);

    points[i][0] = points[i][0] + 80;
    points[i][1] = 64 - points[i][1];  // position (0,0,0) in center
                                       // - visible area is 160x128
  }

  // draw the "cube"
  WinDrawLine(points[0][0], points[0][1], points[1][0], points[1][1]);
  WinDrawLine(points[0][0], points[0][1], points[3][0], points[3][1]);
  WinDrawLine(points[0][0], points[0][1], points[4][0], points[4][1]);
  WinDrawLine(points[2][0], points[2][1], points[1][0], points[1][1]);
  WinDrawLine(points[2][0], points[2][1], points[3][0], points[3][1]);
  WinDrawLine(points[2][0], points[2][1], points[6][0], points[6][1]);
  WinDrawLine(points[5][0], points[5][1], points[1][0], points[1][1]);
  WinDrawLine(points[5][0], points[5][1], points[4][0], points[4][1]);
  WinDrawLine(points[5][0], points[5][1], points[6][0], points[6][1]);
  WinDrawLine(points[7][0], points[7][1], points[3][0], points[3][1]);
  WinDrawLine(points[7][0], points[7][1], points[4][0], points[4][1]);
  WinDrawLine(points[7][0], points[7][1], points[6][0], points[6][1]);
}

/**
 * Terminate the Cube3D engine.
 */
void   
CubeTerminate()
{
  CubeGlobals *globals;

  // get a globals reference
  FtrGet(appCreator, ftrCubeGlobals, (UInt32 *)&globals);

  // clean up memory
  MemPtrFree(globals);

  // unregister global data
  FtrUnregister(appCreator, ftrCubeGlobals);
}

/**
 * Perform a simple perspective translation of a 3D point to 2D.
 *
 * @param x the x co-ordinate of the source point
 * @param y the y co-ordinate of the source point
 * @param z the z co-ordinate of the source point
 * @param newX the x co-ordinate pointer of the rotated point
 * @param newY the y co-ordinate pointer of the rotated point
 */
static void 
CubePerspectiveConversion(Coord     x, Coord     y, Coord     z, 
                          Coord *newX, Coord *newY)
{
  fixed _x, _y, _z;

  // NB: 333 and 250 value combinations give a good depth of field :))

  _x = itofx(x);
  _y = itofx(-y);
  _z = Addfx(itofx(-z), itofx(333));

  *newX = (Coord)fxtoi(Divfx(Mulfx(itofx(250), _x), _z));
  *newY = (Coord)fxtoi(Divfx(Mulfx(itofx(250), _y), _z));
}

/**
 * Rotate a 3D point around the X plane.
 *
 * @param x the x co-ordinate of the source point
 * @param y the y co-ordinate of the source point
 * @param z the z co-ordinate of the source point
 * @param newX the x co-ordinate pointer of the rotated point
 * @param newY the y co-ordinate pointer of the rotated point
 * @param newZ the z co-ordinate pointer of the rotated point
 * @param d the angle of rotation.
 */
static void 
CubeRotationAboutX(Coord     x, Coord     y, Coord     z, 
                   Coord *newX, Coord *newY, Coord *newZ, Int16 d)
{
  *newX = x;
  *newY = (Coord)fxtoi(Addfx(Mulfx( cos_fx(d),itofx(y)),Mulfx( sin_fx(d),itofx(z))));
  *newZ = (Coord)fxtoi(Addfx(Mulfx(-sin_fx(d),itofx(y)),Mulfx( cos_fx(d),itofx(z))));
}

/**
 * Rotate a 3D point around the Y plane.
 *
 * @param x the x co-ordinate of the source point
 * @param y the y co-ordinate of the source point
 * @param z the z co-ordinate of the source point
 * @param newX the x co-ordinate pointer of the rotated point
 * @param newY the y co-ordinate pointer of the rotated point
 * @param newZ the z co-ordinate pointer of the rotated point
 * @param d the angle of rotation.
 */
static void 
CubeRotationAboutY(Coord     x, Coord     y, Coord     z, 
                   Coord *newX, Coord *newY, Coord *newZ, Int16 d)
{
  *newX = (Coord)fxtoi(Addfx(Mulfx( cos_fx(d),itofx(x)),Mulfx(-sin_fx(d),itofx(z))));
  *newY = y;
  *newZ = (Coord)fxtoi(Addfx(Mulfx( sin_fx(d),itofx(x)),Mulfx( cos_fx(d),itofx(z))));
}

/**
 * Rotate a 3D point around the Z plane.
 *
 * @param x the x co-ordinate of the source point
 * @param y the y co-ordinate of the source point
 * @param z the z co-ordinate of the source point
 * @param newX the x co-ordinate pointer of the rotated point
 * @param newY the y co-ordinate pointer of the rotated point
 * @param newZ the z co-ordinate pointer of the rotated point
 * @param d the angle of rotation.
 */
static void 
CubeRotationAboutZ(Coord     x, Coord     y, Coord     z, 
                   Coord *newX, Coord *newY, Coord *newZ, Int16 d)
{
  *newX = (Coord)fxtoi(Addfx(Mulfx( cos_fx(d),itofx(x)),Mulfx( sin_fx(d),itofx(y))));
  *newY = (Coord)fxtoi(Addfx(Mulfx(-sin_fx(d),itofx(x)),Mulfx( cos_fx(d),itofx(y))));
  *newZ = z;
}
