/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 *   Mupen64plus - demo.c                                                  *
 *   Mupen64Plus homepage: https://mupen64plus.org/                        *
 *   Copyright (C) 2008 Marshallh                                          *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 *   This program is distributed in the hope that it will be useful,       *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 *   GNU General Public License for more details.                          *
 *                                                                         *
 *   You should have received a copy of the GNU General Public License     *
 *   along with this program; if not, write to the                         *
 *   Free Software Foundation, Inc.,                                       *
 *   51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.          *
 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

/* Use F3DEX microcode. */
#define  F3DEX_GBI

/* Include N64 defines. */
#include <ultra64.h>

#include "include/config.h"
#include "include/helpfunc.h"
#include "include/types.h"

#include "graphics.h"
#include "sprite.h"
#include "hardware.h"
#include "control.h"

#define PI 3.141592654
#define DEG90 PI/2
#define DEG180 PI

/* Macros */
#define vecCopy(A,B) B[0]=A[0]; B[1]=A[1]; B[2]=A[2];
#define vecScale(A,B,C) C[0]=A[0]*(B); C[1]=A[1]*(B); C[2]=A[2]*(B);
#define vecAdd(A,B,C) C[0]=A[0]+B[0]; C[1]=A[1]+B[1]; C[2]=A[2]+B[2];
#define vecSet(V,X,Y,Z) V[0] = X; V[1] = Y; V[2] = Z;

#define DEG2RAD(f) (f) / 180 * PI
#define RAD2DEG(f) (f) * 180 / PI

void renderObj(Gfx* displayList, vec3_t* pos, vec3_t* rot, float size, Gfx** listEnd);

/* Declare objects. */
simpleObj camera;
vec3_t camera_dest;
vec3_t camera_pdest;
float camera_lag = 1.0;
float move_lag = 1.0;

simpleObj logo;
simpleObj box;

Dynamic* generate; /* Dynamic info we're generating now. */
int entity = 0; 

/* Graphics variables. */
int firstframe = 1; /* First frame. */
Gfx* gdl_start;     /* Start of this frame's dynamic display list. */
Gfx* gdl_end;       /* Current position in dynamic display list. */
OSTask* gentask;    /* Task we're generating. */
u16 pnorm;          /* Perpsective normal. */

/* Controller variables. */
int analogX = 0;
int analogY = 0;
int button[4];
OSContPad** pad;
int lockCamera = 0;

char tempStr[256];

float f;
int i;
int j;
int k;
int l;
int dist;
int x;
int y;
int lastx = 0;
int lasty = 0;

/* Effect variables. */
int fade_trans = 0;
float loopStart = 0;
float loopEnd = 0;
float loopTime = 0;
float speed = 1;
float sineCount = 0;
float angle1;
float angle2;

/* Custom variables. */
float customVar1 = 0;
float interp;
float temp;
float rotate = 0;
float fov = 50;
float count = 0;
short stage = 0;
short nextStage = 0;
float logoscale = 0.5;
int logotrans = 1;
int logoenv = 190;

LookAt logola;
Mtx logomat;

short buttonPress[255];

void boot(void* arg)
{
    void MainProc(void*);
    register int i;

    /* Init OS. */
    osInitialize();

    /* Screen off. */
    osViBlack(TRUE);

    /* Create main thread. */
    osCreateThread(&mainThread, MAIN_ID, MainProc, arg, (void*)(mainStack+STACKSIZE/8), (OSPri) MAIN_PRIORITY);

    /* Start main thread. */
    osStartThread(&mainThread);
}

void InitGlobalVars(void)
{
    /* Initialize RSP Addresses. */
    rsp_static_len  = _rsp_staticSegmentRomEnd - _rsp_staticSegmentRomStart;
    rsp_static_addr = (char*) _codeSegmentEnd;
    rsp_static_end  = (char*) _codeSegmentEnd + rsp_static_len;

    fifosize = (u8*)fifobuffer + (sizeof(u64) * FIFOSIZE) + 1;
}

void MainProc(void *arg)
{
    void GameProc(void*);

    InitGlobalVars();

    /* Create game thread. */
    osCreateThread(&gameThread, GAME_ID, GameProc, arg, (void *)(gameStack+STACKSIZE/8), GAME_PRIORITY);

    /* Create video manager. */
    osCreateViManager(OS_PRIORITY_VIMGR);

    /* Set video mode. */
    osViSetMode(&osViModeTable[SCREEN_MODE]);
    osViSetSpecialFeatures(OS_VI_DITHER_FILTER_OFF | OS_VI_GAMMA_OFF);

    /* Create PI manager. */
    osCreatePiManager((OSPri) OS_PRIORITY_PIMGR, &PiMessageQ, PiMessages,           PI_MSGQUEUE_SIZE);

    /* Start game thread. */
    osStartThread(&gameThread);

    /* Relinquish CPU. */
    osSetThreadPri(NULL, 0);

    for(;;);
}

void GameProc(void *arg)
{
    void GameLoop();
    OSIoMesg dmaMb;    
    Gfx* l;

    /* Create DMA message queue. */
    osCreateMesgQueue(&DMAMessageQ, DMAMessages, DMA_MSGQUEUE_SIZE);

    /* Create RSP message queue. */
    osCreateMesgQueue(&RSPDoneMessageQ, RSPDoneMessages, RSPDONE_MSGQUEUE_SIZE);
    osSetEventMesg(OS_EVENT_SP, &RSPDoneMessageQ, dummyMsg);

    /* Create RDP message queue. */
    osCreateMesgQueue(&RDPDoneMessageQ, RDPDoneMessages, RDPDONE_MSGQUEUE_SIZE);
    osSetEventMesg(OS_EVENT_DP, &RDPDoneMessageQ, dummyMsg);

    /* Create VRT message queue. */
    osCreateMesgQueue(&RetraceMessageQ, RetraceMessages, RETRACE_MSGQUEUE_SIZE);
    osViSetEvent(&RetraceMessageQ, dummyMsg, 1);

    /* SI (controllers) */
    osCreateMesgQueue(&SiMessageQ, &SiMessages, 1);
    osSetEventMesg(OS_EVENT_SI, &SiMessageQ, (OSMesg)1);

    initControllers();
    initWorld();

    GameLoop();
}

void initWorld()
{
    vecSet(logo.pos, 0, 0, 0);
    vecSet(box.pos, 0, 0, 0);

    camera.pos[0] = 0;
    camera.pos[1] = 20;
    camera.pos[2] = -300;

    camera_dest[0] = 0;
    camera_dest[1] = 0; 
    camera_dest[2] = 0;
}

void loop1()
{
    loopStart = (int)osGetTime() / 2100;
    /* OS_CYCLES_TO_USEC(osGetTime()); */

    /* Run task we built during the previous frame. */
    if(!firstframe)
        osSpTaskStart(gentask);

     /* Point to new dynamic buffer. */
     generate = &(dynamic[CFB_NextCount]);

     /* Reset entity counter. (matrix and rotation object counter) */
     entity = 0;

     /* Set up pointers to GDL. */
     gdl_start = generate->glist;
     gdl_end = gdl_start;

      /* Init the GDL. */
      N64initDL (&gdl_end, gdl_start, rsp_static_addr, generate, Next_CFB, YES, YES);

      checkCont();
      checkCamera();
}

void checkCont()
{
    /* Read from the joypad controllers. */
    readController();

    button[0]=controllerData[0].button;
    analogX=(int)(controllerData[0].stick_x);
    analogY=(int)(controllerData[0].stick_y);

    /* Deadzone */
    if(fabs(analogX) < 10) {analogX = 0;}
    if(fabs(analogY) < 10) {analogY = 0;}

    if(analogX > 45) analogX = 45;
    if(analogX < -45) analogX = -45;
    if(analogY > 45) analogY = 45;
    if(analogY < -45) analogY = -45;
}

void checkCamera()
{
    Mtx ma;

    camera.mov[0] = camera.pos[0];
    camera.mov[1] = camera.pos[1];
    camera.mov[2] = camera.pos[2];

    camera_pdest[0] = camera_dest[0];
    camera_pdest[1] = camera_dest[1];
    camera_pdest[2] = camera_dest[2];

    /* Set up projection matrix. */
    guPerspective(&(generate->proj), &pnorm, fov, 1.3333333, 50.0, 5000.0, 1.0);
    gSPPerspNormalize(gdl_end++, pnorm);

    guLookAt(&(generate->view), camera.mov[0], camera.mov[1], camera.mov[2], camera_pdest[0], camera_pdest[1], camera_pdest[2], 0.0, 1.0, 0.0);

    guLookAtReflect(&logomat, &logola, camera.mov[0], camera.mov[1], camera.mov[2], camera_pdest[0], camera_pdest[1], camera_pdest[2], 0.0, 1.0, 0.0);

    gSPMatrix(gdl_end++, OS_K0_TO_PHYSICAL(&(generate->proj)), G_MTX_PROJECTION | G_MTX_LOAD | G_MTX_NOPUSH);
    gSPMatrix(gdl_end++, OS_K0_TO_PHYSICAL(&(generate->view)), G_MTX_PROJECTION | G_MTX_MUL | G_MTX_NOPUSH);
}


void drawSprites()
{
    gDPPipeSync(gdl_end++);

    SpriteInit(&gdl_end);


    /* Fade to black/ white. */
    if(fade_trans != 0)
        {
        if(fade_trans > 0)
            drawSprite(&gdl_end, &fade_white, 0, 0, 32, 32, 10, 7.5, fade_trans);
        else
            drawSprite(&gdl_end, &fade_black, 0, 0, 32, 32, 10, 7.5, -fade_trans);
        }

    SpriteFinish(&gdl_end);
}

void loop2()
{
    drawSprites();

    _gdlend:

    /* End the GDL. */
    N64terminateDL(&gdl_end);

    /* Init task. */
    gentask = &(task[CFB_NextCount]);
    N64initTask(gentask, gdl_end, gdl_start);

    /* Flush cache. */
    osWritebackDCacheAll();

    /* Wait for previous task to finish. */
    N64waitRSP(firstframe);
    N64waitRDP(firstframe);

    _alldone:

    /* Swap buffers. */
    N64swapCFB(firstframe);

    loopEnd = (int)osGetTime() / 2100 ;
    /* OS_CYCLES_TO_USEC(osGetTime()); */

    loopTime = loopEnd - loopStart;

    speed = (float)loopTime / 100.0;

    /* Wait for vertical retrace. */
    N64waitVRT();

    firstframe = 0;
}

void GameLoop()
{
    float a_mag;

    while (1)
        {
        loop1();

        count++;

        if(stage == 3)
            {
            logoenv = 190;
            fov = 90;
            vecSet(camera.pos, 0, -200, -170);
            vecSet(camera_dest, 0, 0, 0);

            if(count > 1) {logotrans = 255;}

            camera.pos[0] = -(count*0.5-160);
            camera.pos[1] = (-count*0.1+30);
            camera.pos[2] = -100;

            camera_dest[0] = -(count*0.5-200)+100;
            camera_dest[1] = 0;
            camera_dest[2] = count/3;

            if(count > 568)
                logotrans = (int)(32-(count-568))*8;

            if(count >= 600 || nextStage == 1)
                {
                count = 0;
                stage = 0;
                }
            }

        if(stage == 2)
            {
            logoenv = 0;
            fov = 30;
            vecSet(camera.pos, 0, -200, -170);
            vecSet(camera_dest, 0, 0, 0);

            if(count > 1) {logotrans = 255;}

            if(count > 550)
                fov = (count-550)*0.1+30;

            camera.pos[0] = -(count*0.5-200);
            camera_dest[0] = (count*0.5-200);

            if(count > 568)
                logotrans = (int)(32-(count-568))*8;

            if(count >= 600 || nextStage == 1)
                {
                count = 0;
                stage = 3;
                }
            }

        if(stage == 1)
            {
            logoenv = 190;
            vecSet(camera.pos, 0, 20, -300);
            vecSet(camera_dest, 0, 0, 0);

            if(count > 1) {logotrans = 255;}
            if(count < 40)
                {
                fov = count/3+10;
                logotrans = count*6.375;
                }

            if(count > 300 && count < 350)
                fov += 0.2;

            if(count > 426)
                logotrans = (int)(64-(count-426))*4;

            camera_dest[0] = (count/1.2-220);

            if(count >= 490 || nextStage == 1)
                {
                count = 0;
                stage = 2;
                logotrans = 1;
                }
            }

        if(stage == 0)
            {
            logoenv = 0;
            logo.rot[1] = 0;
            vecSet(camera.pos, 0, 20, -300);
            vecSet(camera_dest, 0, 0, 0);

            camera.pos[0] = (count-300)/4;
            camera.pos[1] = (count-300)/8;

            if(count > 1) {logotrans = 255;}

            if(count <= 40) 
                {
                logoscale = (count/1600)+0.15;
                logotrans = count*6.375;
                }

            if(count > 560)
                logotrans = (int)(64-(count-560))*4;

            if(count >= 624 || nextStage == 1)
                {
                count = 0;
                stage = 1;
                logotrans = 1;
                }
            }

        nextStage = 0;

        guPosition( &(logomat), 90+logo.rot[0], logo.rot[1], logo.rot[2]-180, logoscale, logo.pos[0], logo.pos[1], logo.pos[2]);

        /* Render Objects. */
        renderObj(Wtx_box_box_dl, box.pos, box.rot, 0.5, &gdl_end);
        gDPPipeSync(gdl_end++);
        gDPSetRenderMode(gdl_end++, G_RM_ZB_OPA_SURF, G_RM_ZB_OPA_SURF2);

        if(logotrans == 255)
            renderObj(Wtx_logo_logo_dl, logo.pos, logo.rot, logoscale, &gdl_end);

        /* Chnage render mode to transparent solids. */
        gDPPipeSync(gdl_end++); /* Always call PipeSync before changing rendermodes. */

        gDPSetCombineMode(gdl_end++,G_CC_MODULATERGB_PRIM, G_CC_MODULATERGB_PRIM);
        gDPSetRenderMode(gdl_end++, G_RM_ZB_XLU_SURF, G_RM_ZB_XLU_SURF2);
        gDPSetBlendColor(gdl_end++,0xff, 0xff, 0xff, 0x01);

        gDPSetPrimColor(gdl_end++,0, 0, 255, 255, 255, logotrans);

        if(logotrans < 255)
            renderObj(Wtx_logo_logo_dl, logo.pos, logo.rot, logoscale, &gdl_end);

        /* Environmental Mapping. */
        gDPPipeSync(gdl_end++);
        gDPSetRenderMode( gdl_end++, G_RM_ZB_XLU_SURF,G_RM_ZB_XLU_SURF2);
        gDPSetCombineMode(gdl_end++, G_CC_MODULATERGB_PRIM, G_CC_MODULATERGB_PRIM);
        gSPSetGeometryMode(gdl_end++, G_LIGHTING | G_TEXTURE_GEN);
        gDPSetPrimColor( gdl_end++, 0, 0, 255, 255, 255, (float)logoenv*((float)logotrans/255.0));
        gSPTexture(gdl_end++, 31<<6, 31<<6, 0, G_TX_RENDERTILE, G_ON);

        /* Position and render environment map model. */
        gSPMatrix(gdl_end++, &(logomat), G_MTX_MODELVIEW | G_MTX_LOAD | G_MTX_NOPUSH);
        gSPDisplayList(gdl_end++, Wtx_logo_env_logo_env_dl);

        /* Back to normal. */
        gSPClearGeometryMode(gdl_end++, G_LIGHTING | G_TEXTURE_GEN);

        loop2();
        }
}

void renderObj(Gfx* displayList, vec3_t* pos, vec3_t* rot, float size, Gfx** DL_Pos)
{
    /* Grab the DL. */
    Gfx* glistp;
    glistp = *DL_Pos;

    /* Translate and rotate the object. */
    guPosition(&(generate->xform[entity]), 90+(*rot)[0], (*rot)[1], (*rot)[2]-90+270, size, (*pos)[0], (*pos)[1], (*pos)[2]);

    gSPMatrix(glistp++, OS_K0_TO_PHYSICAL(&(generate->xform[entity])), G_MTX_MODELVIEW | G_MTX_LOAD | G_MTX_NOPUSH);

    gSPDisplayList(glistp++, displayList);

    /* Give the DL back. */
    *DL_Pos = glistp;

    entity++;
}

