/*---------------------------------------------------------------------*
        Copyright (C) 1998 Nintendo. (Originated by SGI)
        
        $RCSfile: morphfaces.c,v $
        $Revision: 1.12 $
        $Date: 1998/12/24 15:31:51 $
 *---------------------------------------------------------------------*/
/*
 * morphfaces.c -- Demonstration of morphing faces.
 *
 * Features:
 *   - Real time vertex morphing
 *   - CPU work (morphing) is overlapped with rendering
 *   - 2-pass blending
 *
 * (c) 1995, Silicon Graphics
 *
 * Author: Bill Mark, July 1995
 */

#include <ultra64.h>
#include "morphfaces.h"

/* Threads */
static OSThread mainThread;  /* Started from boot code; becomes idle thread */
static OSThread gameThread;  /* Thread that eventually does all work */

/* Thread priorities */
#define MAIN_PRIORITY 10
#define GAME_PRIORITY 10

/* Thread ID's -- Used only with debugger -- they're arbitrary */
#define MAIN_ID 3
#define GAME_ID 4

u64 bootStack[STACKSIZE/8];  /* For boot code.  Can reuse on boot code exit. */
u64 mainStack[STACKSIZE/8];  /* Stacks for threads... */
u64 gameStack[STACKSIZE/8];

/* Message queue lengths */
#define PI_MSGQUEUE_SIZE   8 /* Not sure what the 'correct' value is.  Should
                                at least roughtly match possible count of
				outstanding PI requests */
#define DMA_MSGQUEUE_SIZE     1
#define RDPDONE_MSGQUEUE_SIZE 1
#define RETRACE_MSGQUEUE_SIZE 1

/* Message queues */
static OSMesg      PiMessages[PI_MSGQUEUE_SIZE];
static OSMesgQueue PiMessageQ;
static OSMesg      DMAMessages[DMA_MSGQUEUE_SIZE];
static OSMesgQueue DMAMessageQ;
static OSMesg      RDPDoneMessages[RDPDONE_MSGQUEUE_SIZE];
static OSMesgQueue RDPDoneMessageQ;
static OSMesg      RetraceMessages[RETRACE_MSGQUEUE_SIZE];
static OSMesgQueue RetraceMessageQ;


/* Dummy message */
static OSMesg      dummyMsg;

/*
 * Symbols generated by "makerom" to tell us where various segments are
 * in cartridge ROM, and in RDRAM.
 */
extern char _rsp_staticSegmentRomStart[],  _rsp_staticSegmentRomEnd[];
extern char _cfbSegmentStart[];
extern char _codeSegmentEnd[];


/*
 * Display lists defined in static.c
 */
extern Gfx rspinit_dl[];
extern Gfx rdpinit_dl[];
extern Gfx scrnclr_dl[];
extern Gfx shadetri_dl[];

/*
 * Z-Buffer from zbuf.c; Color Frame Bufs from cfb.c
 */
extern unsigned short zbuffer[];
extern unsigned short cfb_A[];
extern unsigned short cfb_B[];

/*
 * Double-buffered data structures
 * Must be declared global (rather than local to a routine) so that they
 * don't disappear while they're in use when routine exits.
 */
dynamic_stuff dynamic[2];  /* Declare two copies of dynamic stuff */
OSTask        task[2];     /* Two copies of task structure */
extern dynamic_stuff rsp_dynamic;  /* RSP Addresses for dynamic segment */

/*
 * Protypes for routines in this file
 */
void boot(void *arg);
static void mainproc(void *arg);
static void gameproc(void *arg);
static void gameloop(char *rsp_static_addr);
static void drawframe(dynamic_stuff *generate, Gfx **glistpParam, float t);


/*
 * Protypes for routines outside this file
 */
void morph(Vtx **valist, float *weights, int vacnt, Vtx *vout, int vcnt,
	   int alpha, int zdist);
void makestuff(void);

/*
 * Face Verticies (original); should be approximately -250 -> +250 x & y
 */
extern Vtx vface1[];
extern Vtx vface2[];

/*
 * Triangles to draw -- used by makestuff to build verticies
 */
extern int tricnt;
extern int trilist[];  /* format: i, i+1, i+2 = indicies for tri */

/*
 * Texture coordinates to be SetTileSize'd with triangles
 */
extern int tcoords1[][4];
extern int tcoords2[][4];

/*
 * Textures (static segment)
 */
extern int tindex1[];
extern int tindex2[];
extern unsigned int texture[];

/*
 * Display lists -- filled in by makestuff
 */
#define DLSIZE 4000
Gfx dlist1[DLSIZE];
Gfx dlist2[DLSIZE];

OSPiHandle	*handler;

/*
 * boot -- first routine called
 */
void boot(void *arg) {
  osInitialize();

  handler = osCartRomInit();

  osCreateThread(&mainThread, MAIN_ID, (void(*)(void *))mainproc, arg,
		 (void *)(mainStack+STACKSIZE/8), (OSPri) MAIN_PRIORITY);
  osStartThread(&mainThread);
} /* boot */

/*
 * mainproc -- invoked by boot.  Sets up, then becomes idle thread.
 */
static void mainproc(void *arg) {
  /* Note:  Not using TLB -- all our R4300 address are KSEG0 (physical) */
  
  osCreateThread(&gameThread, GAME_ID, gameproc, arg,
		 (void *)(gameStack+STACKSIZE/8), GAME_PRIORITY);

  /* Start VI Mgr (Video) */
  osCreateViManager(OS_PRIORITY_VIMGR);

  /* Set video mode to Low Res; Anti-Aliased; Non-interlaced; 16 bits/pixel
   * Related values: 1) #define's for SCREEN_WD and HT in morphfaces.h
   *                 2) Element sizes in cfb.c
   *                 3) Address for cfb in 'spec'
   *                 4) gsDP clear instructions in static.c
   */
#ifdef FB32BIT
  osViSetMode(&osViModeTable[OS_VI_NTSC_LAN2]);
#else
  osViSetMode(&osViModeTable[OS_VI_NTSC_LAN1]);
#endif

  /* Start PI Mgr for access to cartridge
   * This call creates the message queue, so we don't have to.
   */
  osCreatePiManager((OSPri) OS_PRIORITY_PIMGR, &PiMessageQ, PiMessages,
		    PI_MSGQUEUE_SIZE);

  osStartThread(&gameThread);

  /* Become the idle thread and relinquish CPU */
  osSetThreadPri(NULL, 0);
  for(;;); /* This in fact just spins, but OK since we're at priority 0 */
} /* mainproc */

/*
 * gameproc -- entry point for permanently running primary game thread
 */

static void gameproc(void *arg) {
  char     *rsp_static_addr;  /* R4300 addr where rsp_static segment loaded */
  char     *rsp_static_end;   /* R4300 addr+1 for end of the load region    */
  int      rsp_static_len;
  OSIoMesg dmaMb;             /* Needed (empty) for call to osEPiStartDma    */

  /* Message Queue for OS messages indicating DMA completions */
  osCreateMesgQueue(&DMAMessageQ, DMAMessages, DMA_MSGQUEUE_SIZE);

  /* Message Queue for OS messages indicating RDP done */
  osCreateMesgQueue(&RDPDoneMessageQ, RDPDoneMessages, RDPDONE_MSGQUEUE_SIZE);
  osSetEventMesg(OS_EVENT_DP, &RDPDoneMessageQ, dummyMsg);

  /* Message Queue for OS messages indicating vertical retrace */
  osCreateMesgQueue(&RetraceMessageQ, RetraceMessages, RETRACE_MSGQUEUE_SIZE);
  osViSetEvent(&RetraceMessageQ, dummyMsg, 1);

  /*
   * Load the 'rsp_static' segment from the cartridge
   */
  rsp_static_len  = _rsp_staticSegmentRomEnd - _rsp_staticSegmentRomStart;

  /* Place it right after the code/data segment in the 3rd MB of memory.
   * Will not collide with CFB's or the Z buffer. Because the texture loads
   * are so frequent, this gives a significant performance enhancement.
   */

  rsp_static_addr = _codeSegmentEnd; /* R4300 address for static segment */
  rsp_static_end  = _codeSegmentEnd + rsp_static_len;

#ifdef DEBUG
  if (rsp_static_len > 0x00300000) {
    osSyncPrintf("** Static Segment too large **\n");
  }
#endif

  /* Note: dmaMb is working space used by osEPiStartDma */

  dmaMb.hdr.pri      = OS_MESG_PRI_NORMAL;
  dmaMb.hdr.retQueue = &DMAMessageQ;
  dmaMb.dramAddr     = rsp_static_addr;
  dmaMb.devAddr      = (u32)_rsp_staticSegmentRomStart;
  dmaMb.size         = rsp_static_len;

  osEPiStartDma(handler, &dmaMb, OS_READ);
  osRecvMesg(&DMAMessageQ, NULL, OS_MESG_BLOCK);  /* Wait for completion */

  /*
   * Do the one-time setup
   * (Build both display lists)
   */
  makestuff();

  /*
   * Run the main loop of the game
   */
  gameloop(rsp_static_addr);

} /* gameproc */

/*
 * gameloop -- main loop of game
 *             Called by gameproc; runs in gameThread.
 *             Parameter: rsp_static_addr, the R4300 address where the
 *                        rsp_static segment was loaded
 */
static void gameloop(char *rsp_static_addr) {
  int oddframe = 0;        /* Odd or even rendered frame?  Start with even. */
  int firstframe = 1;      /* First frame? */
  dynamic_stuff *generate; /* Dynamic info we're generating now             */
  Gfx           *glistp0;  /* Start of this frame's dynamic display list */
  Gfx           *glistp;   /* Current position in dynamic display list */
  OSTask        *gentask;  /* Task we're generating */
  float         t;         /* Weight of 1st set of morph verticies */
  int           dir;       /* Direction in which we're morphing */
  int           pausecnt;  /* Countdown for "stickiness" at morph extremes */

  t = 0.0;
  dir = 1;
  pausecnt = 0;
  while (1) {
    /* Start task on RSP, built in previous iteration of loop. */
    if (!firstframe) osSpTaskStart(gentask);

    /*
     * Set up pointers to dynamic stuff.  We're always generating one set
     * of dynamic data and drawing another.
     */
    generate = &(dynamic[(oddframe ? 0 : 1)]);

    /* glist portion of generated dynamic data */
    glistp0 = &(generate->glist[0]);
    glistp  = glistp0;  /* This one will be incremented as list is built */
    
    /*
     * Tell RSP where each segment is
     */
    gSPSegment(glistp++, 0, 0x0);     /* Physical address segment */
    /* Static segment (mapping never changes) */
    gSPSegment(glistp++, STATIC_SEG,  OS_K0_TO_PHYSICAL(rsp_static_addr)); 
    /* Dynamic segment (mapping changes every frame) */
    gSPSegment(glistp++, DYNAMIC_SEG, OS_K0_TO_PHYSICAL(generate));

    /* RSP and RDP setup, and screen clear */
    gSPDisplayList(glistp++, rspinit_dl);
    gSPDisplayList(glistp++, rdpinit_dl);
#ifdef FB32BIT
    gDPSetColorImage(glistp++, G_IM_FMT_RGBA, G_IM_SIZ_32b, SCREEN_WD,
		     OS_K0_TO_PHYSICAL(oddframe ? cfb_A : cfb_B));
#else
    gDPSetColorImage(glistp++, G_IM_FMT_RGBA, G_IM_SIZ_16b, SCREEN_WD,
		     OS_K0_TO_PHYSICAL(oddframe ? cfb_A : cfb_B));
#endif
    gSPDisplayList(glistp++, scrnclr_dl);
    /* Must set color image again after zbuffer clear */
#ifdef FB32BIT
    gDPSetColorImage(glistp++, G_IM_FMT_RGBA, G_IM_SIZ_32b, SCREEN_WD,
		     OS_K0_TO_PHYSICAL(oddframe ? cfb_A : cfb_B));
#else
    gDPSetColorImage(glistp++, G_IM_FMT_RGBA, G_IM_SIZ_16b, SCREEN_WD,
		     OS_K0_TO_PHYSICAL(oddframe ? cfb_A : cfb_B));
#endif
    /* Set Z-buffer */
    gDPSetDepthImage(glistp++, OS_K0_TO_PHYSICAL(zbuffer));

    /*
     * Matrix setup, and call display list that does real work
     */
    drawframe(generate, &glistp, t);

    /*
     * Put an END on the top-level display list, and check that we
     * haven't overflowed the buffer.
     */
    gDPFullSync(glistp++); /* Only need this if you want 'accurate' SP done? */
    gSPEndDisplayList(glistp++);
#ifdef DEBUG
    if ((int)(glistp - glistp0) > GLIST_SIZE) {  /* does this check work?? */
      osSyncPrintf("*** GLIST OVERFLOW ***\n");
      /* Could quit here or something */
    } 
#endif

    /* Update morph parameter t */
    if (pausecnt == 0) {
      t += dir*(1.0/60.0);           /* A full 60 frames to do the morph */
      if (t < 0.0) {t = 0.0; dir =  1; pausecnt = 60;}
      if (t > 1.0) {t = 1.0; dir = -1; pausecnt = 60;}
    } else {
      pausecnt--;
    }

    /*
     * Build graphics task
     * Note that all addresses are KSEG0, even if used by the RSP.
     * Conversion is done by the task routines.
     */
    gentask = &(task[oddframe ? 0 : 1]);
    gentask->t.type            = M_GFXTASK;
    gentask->t.flags           = 0x0;
    gentask->t.ucode_boot      = (u64*) rspbootTextStart;
    gentask->t.ucode_boot_size = ((int)rspbootTextEnd - (int)rspbootTextStart);
    gentask->t.ucode           = (u64*) gspF3DEX2_xbusTextStart; /* use XBUS */
    gentask->t.ucode_data      = (u64*) gspF3DEX2_xbusDataStart;
    gentask->t.ucode_size      = 4096;
    gentask->t.ucode_data_size = 2048;
    gentask->t.dram_stack      = (u64*) dram_stack;
    gentask->t.dram_stack_size = SP_DRAM_STACK_SIZE64;
    gentask->t.output_buff     = (u64*) NULL;
    gentask->t.output_buff_size= (u64*) NULL;
    gentask->t.yield_data_ptr  = (u64*) NULL; /* Graphics only - no yielding */
    gentask->t.yield_data_size = 0x0;
    gentask->t.data_ptr        = (u64*) glistp0;
    gentask->t.data_size       = ((int) glistp - (int) glistp0);
    
    /*
     * Flush the whole cache.  Should just do parts of it.
     */
    osWritebackDCacheAll();
    
    if (!firstframe) {
      /* Wait for task completion (message from RDP) */
      osRecvMesg(&RDPDoneMessageQ, NULL, OS_MESG_BLOCK);
    
      /*
       * Specify frame buffer to be displayed starting at next retrace.
       * We display the one that we've just finished rendering.
       */
      osViSwapBuffer(oddframe ? cfb_B : cfb_A);
    }

    /*
     * Wait for the retrace before rendering next frame
     * (otherwise we might overwrite the frame which is being displayed).
     * But first, make sure that the retrace queue is empty in case we
     * took too long to render the last frame.
     */
    if (MQ_IS_FULL(&RetraceMessageQ))
      osRecvMesg(&RetraceMessageQ, NULL, OS_MESG_NOBLOCK);
    osRecvMesg(&RetraceMessageQ, NULL, OS_MESG_BLOCK);

    /* Switch to our other set of variables */
    oddframe ^= 1;

    /* No longer the first frame */
    firstframe = 0;
 
  } /* while(1) */

} /* gameloop */


/*
 * drawframe --  Set up matricies and draw the frame
 *               (by putting entries in display list)
 *
 * t is the morph pararmater [0.0, 1.0]
 */

static void drawframe(dynamic_stuff *generate, Gfx **glistpParam,
		      float t) {
  Gfx   *glistp;
  Vtx   *valist[2]; /* Array of vertex lists */
  float weights[2]; /* Array of weights      */
  u16   perspnorm;
  int i;
  unsigned char a2;

  glistp = *glistpParam;  /* Copy to local var */
  /*
   * Set up matricies
   */
  /* Z-distance to force for faces */
#define ZDIST (700*4)

  guPerspective(&(generate->projection), &perspnorm,
		60.0, 320.0/240.0,
		ZDIST-500.0, ZDIST+500.0, 1.0);
  gSPPerspNormalize(glistp++, perspnorm);

  /* guRotate(&(generate->modeling_rotate1), 40.0, 1.0, 1.0, 1.0); */
  guTranslate(&(generate->modeling_translate), 0.0, 0.0, 0.0);  /* -... */
  
  gSPMatrix(glistp++, OS_K0_TO_PHYSICAL(&(generate->projection)),
	    G_MTX_PROJECTION|G_MTX_LOAD|G_MTX_NOPUSH);
  gSPMatrix(glistp++, OS_K0_TO_PHYSICAL(&(generate->modeling_translate)),
	    G_MTX_MODELVIEW|G_MTX_LOAD|G_MTX_NOPUSH);
  /* gSPMatrix(glistp++, OS_K0_TO_PHYSICAL(&(generate->modeling_rotate1)),
	    G_MTX_MODELVIEW|G_MTX_MUL|G_MTX_NOPUSH); */

  /*
   * Morph
   */
  valist[0]  = vface1;
  weights[0] = t;
  valist[1]  = vface2;
  weights[1] = 1.0 - t;
  /* Two sets of key verticies, tricnt*3 verticies per set, alpha = 255 */
  /* Texture coords come from vface1 */
  morph(valist, weights, 2, &(generate->tvface1[0]), tricnt*3, 255, -ZDIST);

  /*
   * 2nd set of verticies, same except for different alpha and s,t
   */
  a2 = 255*(1.0-t); /* alpha value */
  for (i=0; i<tricnt*3; i++) {
    generate->tvface2[i] = generate->tvface1[i];
    generate->tvface2[i].v.tc[0] = vface2[i].v.tc[0] << 6;
    generate->tvface2[i].v.tc[1] = vface2[i].v.tc[1] << 6;
    generate->tvface2[i].v.cn[3] = a2;
  } /* for i */

  /* Pass 1 -- opaque */
  gSPSetGeometryMode(glistp++, G_SHADE);
  gDPPipeSync(glistp++);
  gDPSetRenderMode(glistp++, G_RM_AA_ZB_OPA_SURF, G_RM_AA_ZB_OPA_SURF2);
  gSPDisplayList(glistp++, OS_K0_TO_PHYSICAL(dlist1));

  /* Pass 2 -- transparent */
  gSPSetGeometryMode(glistp++, G_SHADE);
  gDPPipeSync(glistp++);
  gDPSetRenderMode(glistp++, G_RM_AA_XLU_SURF, G_RM_AA_XLU_SURF2);
  gSPDisplayList(glistp++, OS_K0_TO_PHYSICAL(dlist2));

  *glistpParam = glistp;  /* Copy back from local var */
} /* drawframe */


/*
 * Makestuff -- make the display lists
 */

void makestuff(void) {
  int i;
  Gfx *gl1;
  Gfx *gl2;
  Gfx *gl1a;

  gl1 = &(dlist1[0]);
  gl1a = gl1;
  gl2 = &(dlist2[0]);

  /*
   * Check for array size problems
   */
#ifdef DEBUG
  if (tricnt > VCNT) {
    osSyncPrintf("Uh Oh -- tricnt > VCNT\n");
  }
#endif

  /*
   * Loop over triangles
   */
  for (i=0; i<tricnt; i++) {
    /*
     * For display list #1...
     *
     * Texture
     */
    gDPLoadTextureBlock(gl1++, &(texture[tindex1[i*3+0]]),
			G_IM_FMT_RGBA,			
			G_IM_SIZ_32b,
		        tindex1[i*3+1], tindex1[i*3+2], /* width, height */
			0,
			0, 0,                     /* clamp,wrap,mirror flags */
			G_TX_NOMASK, G_TX_NOMASK, /* Mask for s,t */
			G_TX_NOLOD, G_TX_NOLOD);  /* Shift for s,t */
    gDPSetTileSize(gl1++,
		   G_TX_RENDERTILE, /* tile # */
		   tcoords1[i][0] << 2,  /* uls, 10.2 */
		   tcoords1[i][1] << 2,  /* ult, 10.2 */
		   tcoords1[i][2] << 2,  /* lrs, 10.2 */
		   tcoords1[i][3] << 2   /* lrt, 10.2 */
		   );
    /*
     * Load verticies and render
     * (Note: Vertex loading could be more efficient)
     */
    gSPVertex(gl1++, &(rsp_dynamic.tvface1[trilist[3*i]]), 1, 0);
    gSPVertex(gl1++, &(rsp_dynamic.tvface1[trilist[3*i+1]]), 1, 1);
    gSPVertex(gl1++, &(rsp_dynamic.tvface1[trilist[3*i+2]]), 1, 2);
    gSP1Triangle(gl1++, 0, 1, 2, 0);

    /*
     * For display list #2...
     *
     * Texture
     */
    gDPLoadTextureBlock(gl2++, &(texture[tindex2[i*3+0]]),
			G_IM_FMT_RGBA,			
			G_IM_SIZ_32b,
			tindex2[i*3+1], tindex2[i*3+2],  /* width, height */
			0,
			0, 0,
			G_TX_NOMASK, G_TX_NOMASK,
			G_TX_NOLOD, G_TX_NOLOD);
    gDPSetTileSize(gl2++, 0,
		   tcoords2[i][0] << 2,  /* uls, 10.2 */
		   tcoords2[i][1] << 2,  /* ult, 10.2 */
		   tcoords2[i][2] << 2,  /* lrs, 10.2 */
		   tcoords2[i][3] << 2   /* lrt, 10.2 */
		   );
    /*
     * Load verticies and render
     * (Note: Vertex loading could be more efficient)
     */
    gSPVertex(gl2++, &(rsp_dynamic.tvface2[trilist[3*i]]), 1, 0);
    gSPVertex(gl2++, &(rsp_dynamic.tvface2[trilist[3*i+1]]), 1, 1);
    gSPVertex(gl2++, &(rsp_dynamic.tvface2[trilist[3*i+2]]), 1, 2);
    gSP1Triangle(gl2++, 0, 1, 2, 0);

    /*
     * Check for close to overrun
     */
    if ((gl1-gl1a) + 50 > DLSIZE) {
#ifdef DEBUG
      osSyncPrintf("Overflowed display list in makestuff -- truncating\n");
#endif
      gSPEndDisplayList(gl1++);
      gSPEndDisplayList(gl2++);
      return;
    }
      

  } /* for i */

  gSPEndDisplayList(gl1++);
  gSPEndDisplayList(gl2++);
} /* makestuff */

