/*----------------------------------------------------------------------------
 *      m.c
 *
 *   This file starts the program and opens the screen and window.
 *----------------------------------------------------------------------------
*/

#include  <exec/types.h>
#include  <intuition/intuition.h>
#include  <lattice/stdio.h>
#include  <graphics/gfxmacros.h>
#include  "defs.h"
#include  "ds.h"

extern BOOL  OpenWorkBench(),  CloseWorkBench() ;
extern struct Menu  *first_menu ;
extern struct View  *ViewAddress() ;

/* pointers to op sys libraries */
struct IntuitionBase  *IntuitionBase = NULL ;
struct GfxBase        *GfxBase       = NULL ;
int                   MathBase       = NULL ;
int                   MathTransBase  = NULL ;

/* pointers to custom screen and window opened and used by the program */
struct Screen  *screen   = NULL,  *OpenScreen() ;
struct Window  *window   = NULL,  *OpenWindow() ;

/* text font in use */
struct TextAttr  prog_font  = 
               { "topaz.font",        /* font name */
                 8,                   /* height of font */
                 FS_NORMAL,           /* font style */
                 FPF_ROMFONT          /* font is in ROM */
               } ;

/* Information used to set up a work area for the ROM graphic routines. */
/* These structures are shared by the window temporary drawing area */
/* and the the 2 screen (double buffered) rastports and bitmaps */
PLANEPTR         rastptr = NULL ;
UWORD            areabuffer [500] ;
struct TmpRas    tmpras ;
struct AreaInfo  areainfo ;

/* information needed to set up double buffering in the screen */
struct View        *v  = NULL ;
struct ViewPort    *vp = NULL ;
struct RasInfo     *ri = NULL ;
struct RastPort    rp,   rp2 ;
struct BitMap      b,    b2 ;

/* color information used to load the color registers */
COLORTABLE  colortable  =
                          /* default colors */
                        { {GREY, RED, WHITE, BLUE,     
                           ORANGE, GREEN, PURPLE, DARK_BLUE},
                          /* set1 colors */
                          {GREY1, RED1, WHITE1, BLUE1,    
                           ORANGE1, GREEN1, PURPLE1, DARK_BLUE1},
                          /* set2 colors */
                          {GREY2, RED2, WHITE2, BLUE2,       
                           ORANGE2, GREEN2, PURPLE2, DARK_BLUE2},
                          /* set3 colors */
                          {GREY3, RED3, WHITE3, BLUE3,       
                           ORANGE3, GREEN3, PURPLE3, DARK_BLUE3},
                          /* colors to clear the screen to background */
                          {BACKGROUND_COLOR, BACKGROUND_COLOR,
                           BACKGROUND_COLOR, BACKGROUND_COLOR,
                           BACKGROUND_COLOR, BACKGROUND_COLOR,
                           BACKGROUND_COLOR, BACKGROUND_COLOR}
                        } ;

/* declaration of the arm data structure */ 
ARM  arm ;

/* FFP constants */
FFP_CONSTANTS  ffp ;

/* viewing transformations available - precalculated at start of program */
/* - based on a viewpoint specified as a point in world space */
VIEW_TRANS  view_trans ;

/* store rotation information for each link */
ROTATION_INFO   rot_info ; 

/* reference axes coordinate positions */
REF_AXES  ref_axes [NUM_LINKS] ;
 
/* record of current user selected menu items */
/* initialized to default values */
MENU_CHOICES  menu_choices  = {
                                 WIRE_FRAME,       /* type of rendering */ 
                                 ANGLE1_VIEW,      /* default viewpoint */
                                 PARALLEL,         /* persective type */
                                 ROTATE,           /* type of animation */
                                 START,            /* color setting */
                                 OFF               /* ref axes setting */
                              } ;


main ()
{
 /*--------------------------------------------------------------------------
  *   Open the required system libraries.  Open a custom screen and a window
  * on the screen.  Initialize a graphics work area, viewing transformations,
  * and rotation information.  The input float vertex coordinates are
  * transferred to FFP format for the duration of the program.
  *    Call "get_input()" to await user input.
  *--------------------------------------------------------------------------
 */
 open_libraries () ;

 allocate_double_buffer_bitmaps () ;
 make_screen () ;
 make_window () ;

 CloseWorkBench () ;

 initialize_requesters () ;

 setup_screen_temporary_draw_areas () ;
 setup_window_temporary_draw_area () ;

 init_FFP_constants () ;
 init_arm_data () ;
 convert_arm_data_to_FFP () ;

 initialize_link_rotation_info () ;
 initialize_viewing_transformations () ;
 init_flypast_trans () ;

 get_input () ;
}


open_libraries ()
{
 /*---------------------------------------------------------------------------
  *   Open required system libraries.
  *---------------------------------------------------------------------------
 */
 IntuitionBase  = (struct IntuitionBase *) OpenLibrary ("intuition.library",
                                                         INTUITION_REV) ;
 if (IntuitionBase == NULL) 
     close_down ("Error: Could not open intuition library\n") ;

 GfxBase  = (struct GfxBase *) OpenLibrary ("graphics.library",
                                             GRAPHICS_REV) ;
 if (GfxBase == NULL)
     close_down ("Error: Could not open graphics library\n") ;     

 MathBase  = OpenLibrary ("mathffp.library", MATH_REV) ;

 if (MathBase == NULL)
     close_down ("Error: Could not open mathffp library\n") ;    

 MathTransBase  = OpenLibrary ("mathtrans.library", MATHTRANS_REV) ;

 if (MathTransBase == NULL)
     close_down ("Error: Could not open mathtrans library\n") ; 
}


make_screen ()
{
 /*---------------------------------------------------------------------------
  *   Setup a screen data structure and call OpenScreen() to open a custom
  * screen.  The NewScreen data structure is not required once the screen is
  * open, ie. it disappears on the program stack.
  *   Since double buffering is being used, a custom bitmap is connected to the
  * screen.
  *   Load "background" colors into all relevant color registers to ease the
  * transition from custom screen to window.
  *---------------------------------------------------------------------------
 */
 struct NewScreen NewScreen ;

 NewScreen.LeftEdge      = 0 ;
 NewScreen.TopEdge       = 0 ;
 NewScreen.Width         = WIDTH ;
 NewScreen.Height        = HEIGHT ;  
 NewScreen.Depth         = DEPTH ;
 NewScreen.DetailPen     = COLOR2 ;
 NewScreen.BlockPen      = COLOR1 ;
 NewScreen.ViewModes     = HIRES ;      /* 640 x 200 pixel screen */
 NewScreen.Type          = CUSTOMSCREEN | CUSTOMBITMAP ;
 NewScreen.Font          = &prog_font ;
 NewScreen.DefaultTitle  = " Double Buffered Animation Screen" ;  
 NewScreen.Gadgets       = NULL ;
 NewScreen.CustomBitMap  = &b ;

 if ((screen  = OpenScreen (&NewScreen)) == NULL)
    close_down ("Error: Could not open Custom Screen - memory shortage?\n") ;

 LoadRGB4 (&screen->ViewPort, colortable.clr_screen, NUM_COLORS) ;
 SetRast (&screen->RastPort, COLOR0) ;
 SetRast (&rp2, COLOR0) ;
 ShowTitle (screen, FALSE) ;

 /* info used later to swap screen bitmaps for double buffering */
 v   = ViewAddress () ;
 vp  = &screen->ViewPort ;
 ri  = vp->RasInfo ;
}
   
 
make_window ()
{
 /*---------------------------------------------------------------------------
  *    Open a window on the screen.  Set up the window menus and load the 
  * color registers with the values to be used by the program.
  *---------------------------------------------------------------------------
 */
 struct NewWindow  NewWindow ;

 NewWindow.LeftEdge     = 0 ;
 NewWindow.TopEdge      = 0 ;
 NewWindow.Width        = WIDTH ; 
 NewWindow.Height       = HEIGHT ;
 NewWindow.DetailPen    = COLOR1 ;
 NewWindow.BlockPen     = COLOR2 ;
 NewWindow.Title        = " Animation Setup Window" ;
 NewWindow.Flags        = ACTIVATE | SMART_REFRESH |
                          NOCAREREFRESH | GIMMEZEROZERO ;  
 NewWindow.IDCMPFlags   = MENUPICK | GADGETUP ; 
 NewWindow.Type         = CUSTOMSCREEN ;
 NewWindow.FirstGadget  = NULL ;
 NewWindow.CheckMark    = NULL ;
 NewWindow.Screen       = screen ;
 NewWindow.BitMap       = NULL ;
 NewWindow.MinWidth     = 0 ;
 NewWindow.MinHeight    = 0 ;
 NewWindow.MaxWidth     = 0 ;
 NewWindow.MaxHeight    = 0 ;

 if ((window = OpenWindow (&NewWindow)) == NULL)
    close_down ("Error: Could not open Window - memory shortage?\n") ; 

 fill_in_menu_item_text () ;
 SetMenuStrip (window, first_menu) ;

 switch (menu_choices.colors)   {
                                                    
   case START: LoadRGB4 (&screen->ViewPort, colortable.start, NUM_COLORS) ;
               break ;
  case SET1:   LoadRGB4 (&screen->ViewPort, colortable.set1, NUM_COLORS) ;
               break ;
  case SET2:   LoadRGB4 (&screen->ViewPort, colortable.set2, NUM_COLORS) ;
               break ;
  case SET3:   LoadRGB4 (&screen->ViewPort, colortable.set3, NUM_COLORS) ;
               break ;
 default:      close_down ("Error: \"make_window\" - bad color menu choice\n");
               break ;
 }
}


close_down (message)

char *message ;
{
 int  i ;
 /*---------------------------------------------------------------------------
  *   Close down the program.  Free all dynamically allocated memory, and 
  * close all opened libraries etc.  If the shut down is due to a system 
  * problem print a message to this effect on the standard error output.
  *---------------------------------------------------------------------------
 */
 if (rastptr) FreeRaster (rastptr, WIDTH, HEIGHT) ;
 if (window)  ClearMenuStrip (window) ;
 if (window) CloseWindow (window) ;
 if (screen) CloseScreen (screen) ;
 for ( i = 0;  i < DEPTH;  ++i)   {
     if (b2.Planes [i])  FreeRaster (b2.Planes[i], WIDTH, HEIGHT) ;
     if (b.Planes [i])   FreeRaster (b.Planes[i], WIDTH, HEIGHT) ;
 }
 if (IntuitionBase)  CloseLibrary (IntuitionBase) ;
 if (GfxBase)  CloseLibrary (GfxBase) ;
 if (MathBase) CloseLibrary (MathBase) ;
 if (MathTransBase) CloseLibrary (MathTransBase) ;
 if (message)  fprintf (stderr, message) ;
 OpenWorkBench () ;
 exit (!!message) ;
}


allocate_double_buffer_bitmaps ()
{
 int  i ;

 /*---------------------------------------------------------------------------
  *   Allocate memory for 2 sets of bitplanes for a double buffered Intuition
  * screen.  Set up a rastport for each bitplane set.  Rastports coordinate
  * drawing information for the bitplane sets.
  *---------------------------------------------------------------------------
 */
 InitBitMap (&b, DEPTH, WIDTH, HEIGHT) ;
 InitBitMap (&b2, DEPTH, WIDTH, HEIGHT) ;

 for (i = 0;  i < DEPTH;  ++i)   {
    b.Planes [i]  = (PLANEPTR) AllocRaster (WIDTH, HEIGHT) ;
    if (b.Planes [i] == NULL)  
       close_down  ("Error: Not enough memory for double buffered screen\n") ;
    b2.Planes [i]  = (PLANEPTR) AllocRaster (WIDTH, HEIGHT) ;
    if (b2.Planes [i] == NULL)
       close_down ("Error: Not enough memory for double buffered screen\n") ;
 }
 /* set up a rastport for each set of bitmaps */
 InitRastPort (&rp) ;
 rp.BitMap   = &b ;
 InitRastPort (&rp2) ;
 rp2.BitMap  = &b2 ;
}


setup_screen_temporary_draw_areas ()
{
 /*---------------------------------------------------------------------------
  *    The area fill draw routines require a temporary workspace.
  * Allocate memory for one and allow both screen rastports to use it, since
  * they will never both require access to it at the same time.
  *---------------------------------------------------------------------------
 */ 
 SetDrMd (&rp, JAM1) ;
 SetOPen (&rp, COLOR7) ;
 SetDrMd (&rp2, JAM1) ;
 SetOPen (&rp2, COLOR7) ;
 InitArea (&areainfo, areabuffer, 200) ;
 rastptr  = (PLANEPTR) AllocRaster (WIDTH, HEIGHT) ;
 if (rastptr == NULL)
    close_down ("Error: Not enough memory for db temp drawing area\n") ;
 InitTmpRas (&tmpras, rastptr, RASSIZE (WIDTH, HEIGHT)) ;
 rp.AreaInfo  =  rp2.AreaInfo  = &areainfo ;
 rp.TmpRas  =  rp2.TmpRas  = &tmpras ;
}


setup_window_temporary_draw_area ()
{
 /*---------------------------------------------------------------------------
  *   As above.  The window rastport shares the same temporoary work area.
  *---------------------------------------------------------------------------
 */
 SetDrMd (window->RPort, JAM1) ;
 SetOPen (window->RPort, COLOR7) ;
 window->RPort->AreaInfo  = &areainfo ;
 window->RPort->TmpRas    = &tmpras ;
}


draw_image (rp)

struct RastPort  *rp ;
{
 /*---------------------------------------------------------------------------
  *   Decide whether to draw a wire frame or solid view, and whether to
  * display the reference axes.
  *---------------------------------------------------------------------------
*/
 SetRast (rp, COLOR0) ;

 switch (menu_choices.render)   {

    case  WIRE_FRAME:      draw_wire_frame_arm (rp) ;
                           break ;
    case  SOLID_VIEW:      draw_solid_arm (rp) ;
                           break ;
 }
 if (menu_choices.ref_axes == ON)
    draw_ref_axes (rp) ;
}


draw_ref_axes (rp)

struct RastPort  *rp ;
{
 int  i ;

 /*---------------------------------------------------------------------------
  *    Draw the current reference axes on the screen.  The endpoints of axes
  * AXES_LENGTH away from a joint origin are transformed (along with the 
  * origin) so that perspective joint axes positions are always available.
  *---------------------------------------------------------------------------
 */
 SetDrMd (rp, JAM2) ;
 SetAPen (rp, COLOR1) ;
 SetBPen (rp, COLOR2) ;

 for (i = 0;  i < NUM_LINKS;  ++i)   {
    
     Move (rp, ref_axes [i].scrn_x [ORIGIN], ref_axes [i].scrn_y [ORIGIN]) ;
     Draw (rp, ref_axes [i].scrn_x [X], ref_axes [i].scrn_y [X]) ;
     Text (rp, "x", 1) ; 

     Move (rp, ref_axes [i].scrn_x [ORIGIN], ref_axes [i].scrn_y [ORIGIN]) ;
     Draw (rp, ref_axes [i].scrn_x [Y], ref_axes [i].scrn_y [Y]) ;
     Text (rp, "y", 1) ; 

     Move (rp, ref_axes [i].scrn_x [ORIGIN], ref_axes [i].scrn_y [ORIGIN]) ;
     Draw (rp, ref_axes [i].scrn_x [Z], ref_axes [i].scrn_y [Z]) ;
     Text (rp, "z", 1) ; 
 }
  SetDrMd (rp, JAM1) ;
}


start_over ()
{
 /*---------------------------------------------------------------------------
  *     Called when the user selects "new" on the Project menu.
  * Reset all original parameters and redraw the initial display.
  *---------------------------------------------------------------------------
 */  
 SetRast (window->RPort, COLOR0) ;
 ClearMenuStrip (window) ;
 reinitialize_menus () ;
 SetMenuStrip (window, first_menu) ;

 menu_choices.render      = WIRE_FRAME ;
 menu_choices.viewpoint   = ANGLE1_VIEW ;
 menu_choices.viewtype    = PARALLEL ;
 menu_choices.goaltype    = ROTATE ;
 menu_choices.colors      = START ;
 menu_choices.ref_axes    = OFF ;

 LoadRGB4 (&screen->ViewPort, colortable.start, NUM_COLORS) ;

 reinitialize_angle_pots () ; 
 initialize_link_rotation_info () ;

 rot_info.curr_view_trans  = (int *) &view_trans.angle1 ;
 transform_links () ;
 set_screen_coords () ;
 draw_image (window->RPort) ;
}


init_FFP_constants ()
{
 /*---------------------------------------------------------------------------
  *   Store frequently used float constants in a form that can be used by the
  * much faster Motorola FFP routines.
  *---------------------------------------------------------------------------
 */
 ffp.zero.f               = 0.0 ;
 ffp.zero.i               = SPFieee (ffp.zero.i) ;
 ffp.one.f                = 1.0 ;
 ffp.one.i               = SPFieee (ffp.one.i) ;
 ffp.neg_one.f           = -1.0 ;
 ffp.neg_one.i           = SPFieee (ffp.neg_one.i) ;
 ffp.two.f                = 2.0 ;
 ffp.two.i                = SPFieee (ffp.two.i) ;
 ffp.screen_scale_x.f     = SCREEN_SCALE_X ;
 ffp.screen_scale_x.i     = SPFieee (ffp.screen_scale_x.i) ;
 ffp.incr_z [0].f         = 0.0 ;
 ffp.incr_z [0].i         = SPFieee (ffp.incr_z [0].i) ;
 ffp.incr_z [1].f         = 72.0 ;
 ffp.incr_z [1].i         = SPFieee (ffp.incr_z [1].i) ;
 ffp.incr_z [2].f         = 124.0 ;
 ffp.incr_z [2].i         = SPFieee (ffp.incr_z [2].i) ;
 ffp.test_pt [0].f         = 0.0 ;
 ffp.test_pt [0].i         = SPFieee (ffp.test_pt [0].i) ;
 ffp.test_pt [1].f         = 0.0 ;
 ffp.test_pt [1].i         = SPFieee (ffp.test_pt [1].i) ;
 ffp.test_pt [2].f         = -1.0 ;
 ffp.test_pt [2].i         = SPFieee (ffp.test_pt [2].i) ;
 ffp.test_pt [3].f         = 0.0 ;
 ffp.test_pt [3].i         = SPFieee (ffp.test_pt [3].i) ;
 ffp.telephoto_aperture.f  = TELEPHOTO_APERTURE ;
 ffp.telephoto_aperture.i  = SPFieee ( ffp.telephoto_aperture.i ) ;
 ffp.wide_angle_aperture.f = WIDE_ANGLE_APERTURE ;
 ffp.wide_angle_aperture.i = SPFieee ( ffp.wide_angle_aperture.i ) ;
 ffp.large_num.f           = 1000000.0 ;
 ffp.large_num.i           = SPFieee (ffp.large_num.i) ;
 ffp.small_num.f           = -1000000.0 ;
 ffp.small_num.i           = SPFieee (ffp.small_num.i) ;
 ffp.screen_fill_x.f       = SCREEN_FILL_X ;
 ffp.screen_fill_x.i       = SPFieee (ffp.screen_fill_x.i) ;
 ffp.screen_fill_y.f       = SCREEN_FILL_Y ;
 ffp.screen_fill_y.i       = SPFieee (ffp.screen_fill_y.i) ;
 ffp.pi.f                  = 3.1415926 ;
 ffp.pi.i                  = SPFieee (ffp.pi.i) ;
 ffp.one_eighty.f          = 180.0 ;
 ffp.one_eighty.i          = SPFieee (ffp.one_eighty.i) ;
 ffp.radians               = SPDiv (ffp.one_eighty.i, ffp.pi.i) ;
 ffp.min_angle_change.f    = MIN_ANGLE_CHANGE ;
 ffp.min_angle_change.i    = SPFieee (ffp.min_angle_change.i) ; 
 ffp.axes_length.f         = AXES_LENGTH ;
 ffp.axes_length.i         = SPFieee (ffp.axes_length.i) ;
}


convert_arm_data_to_FFP ()
{
 LINK_PTR        l ;
 FACE_PTR        f ;
 POINT_PTR       p ;
 FFP_POINT_PTR   fp ;
 FFP             temp ;
 float           *c ;
 int             *c1,  i,  j,  k,  n ;

 /*---------------------------------------------------------------------------
  *    Store the input float arm vertices in FFP format.  These will be used
  * throughout the program instead of the original float values.
  *---------------------------------------------------------------------------
 */
 l  = &arm.link [UPPER] ;

 for (k = 0;  k < NUM_LINKS;  ++k)   {

     init_ref_coords (k) ;
     f  = &l->face[0] ;

     for (i = 0;  i < l->num_faces;  ++i)  {
         p   = &f->vertex [0] ;
         fp  = &f->ffp_orig_vertex [0] ;

         for (j = 0;  j < f->num_vertices;  ++j)    {
             c  = (float *) p++ ;
             c1 = (int *) fp++ ;

             for (n = X;  n <= W;  ++n)  { 
                 temp.f   = *(c+n) ;
                 temp.i   = SPFieee (temp.i) ;
                 if (n == Z) 
                    *(c1+n)  = SPAdd (temp.i, ffp.incr_z [k].i) ;
                 else 
                    *(c1+n)  = temp.i ;
             }
         }
         ++f ;
     }
     ++l ;
 } 
}


init_ref_coords (link) 

int  link ;
{
 int  i ;

 /*---------------------------------------------------------------------------
  *   Initialize the reference axes coordinates.  Each "unit" axis vector is
  * AXES_LENGTH long.  These axes will be transformed along with the arm
  * vertex points and can be displayed on request.
  *---------------------------------------------------------------------------
 */
 for (i = X;  i <= ORIGIN;  ++i) 
     ref_axes [link].orig_coord [i][W]  = ffp.one.i ;

 ref_axes [link].orig_coord [ORIGIN][X]  = ffp.zero.i ;
 ref_axes [link].orig_coord [ORIGIN][Y]  = ffp.zero.i ;
 ref_axes [link].orig_coord [ORIGIN][Z]  = ffp.incr_z [link].i ;

 ref_axes [link].orig_coord [X][X]  = ffp.axes_length.i ;
 ref_axes [link].orig_coord [X][Y]  = ffp.zero.i ;
 ref_axes [link].orig_coord [X][Z]  = ffp.incr_z [link].i ;

 ref_axes [link].orig_coord [Y][X]  = ffp.zero.i ;
 ref_axes [link].orig_coord [Y][Y]  = ffp.axes_length.i ;
 ref_axes [link].orig_coord [Y][Z]  = ffp.incr_z [link].i ;

 ref_axes [link].orig_coord [Z][X]  = ffp.zero.i ;
 ref_axes [link].orig_coord [Z][Y]  = ffp.zero.i ;
 ref_axes [link].orig_coord [Z][Z]  =
                              SPAdd (ffp.incr_z [link].i, ffp.axes_length.i) ;
}
