/*---------------------------------------------------------------------------*
  Project:  NITRO-System - build - demos - g3d - samples - CustomizeSbcOp
  File:     main.c

  Copyright 2004,2005 Nintendo.  All rights reserved.

  These coded instructions, statements, and computer programs contain
  proprietary information of Nintendo of America Inc. and/or Nintendo
  Company Ltd., and are protected by Federal copyright law.  They may
  not be disclosed to third parties or copied or duplicated in any form,
  in whole or in part, without the prior written consent of Nintendo.

  $Log: main.c,v $
  Revision 1.5  02/25/2005 03:09:27  takano_makoto
  Warning measures for signed<->unsigned casts

  Revision 1.4  11/12/2004 05:40:32  nishida
  Revised project name of file header.

  Revision 1.3  11/04/2004 10:43:25  nishida
  Fixed function header of SetCamera function.

  Revision 1.2  09/10/2004 09:30:56  nishida_kenji
  rename NNS_G3dGlbFlush to NNS_G3dGlbFlushP.

  Revision 1.1  08/05/2004 02:35:34  nishida_kenji
  Initial check in.

  $NoKeywords: $
 *---------------------------------------------------------------------------*/

//---------------------------------------------------------------------------
//
// Sample of changing the processing function of the SBC command to one customized by user
//
// Sample Operation
// Each of three squares is displayed:
// without billboard, with billboard, and with Y-axis billboard.
// It is possible to replace the billboard processing function with the one defined in this sample by pressing the A button.
//  By pushing the B button, two-time scaling is applied to the billboard.
// The billboard processing function defined in this sample is faster than the one in the library, but
// cannot display accurately when scaling is applied.
//
// HOWTO:
// * Substitute the user-defined function pointer when supporting the command you wish to
//   customize of the NNS_G3dFuncSbcTable array.
//
//---------------------------------------------------------------------------

#include "g3d_demolib.h"

G3DDemoCamera gCamera;        // Camera structure.
G3DDemoGround gGround;        // Ground structure.

static void          InitG3DLib(void);
static NNSG3dResMdl* LoadG3DModel(const char* path);
static void          SetCamera(G3DDemoCamera* camera);


static void MySbcBBSimple(NNSG3dRS* rs, u32 opt);
static void MySbcBBYSimple(NNSG3dRS* rs, u32 opt);


/* -------------------------------------------------------------------------
  Name:         NitroMain

  Description:  Sample main.

  Arguments:    None.

  Returns:      None.
   ------------------------------------------------------------------------- */
void
NitroMain(void)
{
    NNSG3dRenderObj object;
    NNSG3dResMdl*   model;
    BOOL            useCustomizedBB = FALSE;
    BOOL            isScaled = FALSE;

    G3DDemo_InitSystem();
    G3DDemo_InitMemory();
    G3DDemo_InitVRAM();

    InitG3DLib();
    G3DDemo_InitDisplay();

    // Makes the manager able to manage 4 slots of texture image slots,
    // and sets to the default manager.
    NNS_GfdInitFrmTexVramManager(4, TRUE);

    // Makes the manager able to manage 32 KB of palettes,
    // and sets to the default manager.
    NNS_GfdInitFrmPlttVramManager(0x8000, TRUE);

    model = LoadG3DModel("data/billboard.nsbmd");
    SDK_ASSERTMSG(model, "load failed");

    //
    // G3D: NNSG3dRenderObj initialization
    //
    // NNSG3dRenderObj is a structure that holds every type of information relating to one model and
    // animation applied to that model.
    // The area specified by the internal member pointer must be obtained and released by the user.
    // Here mdl can be NULL. (It must be set during NNS_G3dDraw.)
    //
    NNS_G3dRenderObjInit(&object, model);

    G3DDemo_InitCamera(&gCamera, 0 * FX32_ONE, 4 * FX32_ONE);
    G3DDemo_InitGround(&gGround, (fx32)(1.5 * FX32_ONE));

    G3DDemo_InitConsole();
    G3DDemo_Print(0,0, G3DDEMO_COLOR_YELLOW, "CustomizeSbcOp");
    G3DDemo_Print(0,2, G3DDEMO_COLOR_WHITE, "change:A");
    G3DDemo_Print(0,4, G3DDEMO_COLOR_WHITE, "change:B");

    for(;;)
    {
        OSTick time;

        SVC_WaitVBlankIntr();

        G3DDemo_PrintApplyToHW();
        G3DDemo_ReadGamePad();

        G3DDemo_MoveCamera(&gCamera);
        G3DDemo_CalcCamera(&gCamera);

        G3X_Reset();
        G3X_ResetMtxStack();

        SetCamera(&gCamera);

        {
            VecFx32 scale;

            //
            // scaling of the model is changed with the B button
            //
            if (isScaled)
            {
                scale.x = scale.y = scale.z = 2 * FX32_ONE;
            }
            else
            {
                scale.x = scale.y = scale.z = FX32_ONE;
            }

            NNS_G3dGlbSetBaseScale(&scale);

            //
            // G3D:
            // Collects and transfers the state set with G3dGlbXXXX.
            // Camera and projection matrix, and the like, are set.
            //
            NNS_G3dGlbFlushP();

            //  Start time measurement
            time = OS_GetTick();
            {
                //
                // G3D: NNS_G3dDraw
                // Performs drawing for all instances by passing the
                // NNSG3dRenderObj that was set up.
                NNS_G3dDraw(&object);
            }
            // Time measurement result
            time = OS_GetTick() - time;
        }

        {
            // restore scaling
            VecFx32 scale = {FX32_ONE, FX32_ONE, FX32_ONE};
            NNS_G3dGlbSetBaseScale(&scale);
            //
            // G3D: Before calling normal G3_XXX, NNS_G3dGeComFlushBuffer()
            //      must be called and synchronized
            //
            NNS_G3dGlbFlushP();
            NNS_G3dGeFlushBuffer();
            G3DDemo_DrawGround(&gGround);
        }

        /* memory swap relating to geometry and rendering engine*/
        G3_SwapBuffers(GX_SORTMODE_AUTO, GX_BUFFERMODE_W);

        // change the handler of the SBC billboard command with the A button
        if (G3DDEMO_IS_PAD_TRIGGER(PAD_BUTTON_A))
        {
            if (useCustomizedBB)
            {
                useCustomizedBB = FALSE;
                NNS_G3dFuncSbcTable[NNS_G3D_SBC_BB]  = &NNSi_G3dFuncSbc_BB;
                NNS_G3dFuncSbcTable[NNS_G3D_SBC_BBY] = &NNSi_G3dFuncSbc_BBY;
            }
            else
            {
                useCustomizedBB = TRUE;
                NNS_G3dFuncSbcTable[NNS_G3D_SBC_BB]  = &MySbcBBSimple;
                NNS_G3dFuncSbcTable[NNS_G3D_SBC_BBY] = &MySbcBBYSimple;
            }
        }

        // change the scaling of the model with the B button
        if (G3DDEMO_IS_PAD_TRIGGER(PAD_BUTTON_B))
        {
            if (isScaled)
                isScaled = FALSE;
            else
                isScaled = TRUE;
        }

        if (useCustomizedBB)
        {
            G3DDemo_Printf(0,3, G3DDEMO_COLOR_GREEN, "Use simplified BB Functions");
        }
        else
        {
            G3DDemo_Printf(0,3, G3DDEMO_COLOR_GREEN, "Use default BB Functions   ");
        }

        if (isScaled)
        {
            G3DDemo_Printf(0,5, G3DDEMO_COLOR_GREEN, "Billboards scaled    ");
        }
        else
        {
            G3DDemo_Printf(0,5, G3DDEMO_COLOR_GREEN, "Billboards not scaled");
        }

        G3DDemo_Printf(16,0, G3DDEMO_COLOR_YELLOW, "TIME:%06ld usec", OS_TicksToMicroSeconds(time));
    }
}


/*---------------------------------------------------------------------------*
    MySbcBBSimple
    
    Simplified billboard processing function. High speed, but has the following limitations.
    - No callback point.
    - opt must be NNS_G3D_SBCFLG_000.
    - No support for other than NNS_G3dGlbFlushP.
    - No support for NNS_G3D_RSFLAG_OPT_NOGECMD.
    - Does not display correctly when scaling is applied.
 *---------------------------------------------------------------------------*/
static void
MySbcBBSimple(NNSG3dRS* rs, u32 opt)
{
    //
    // parallel billboard display in the camera projection plane
    //
    static u32 bbcmd1[] =
    {
        ((G3OP_MTX_POP << 0)       |
         (G3OP_MTX_MODE << 8)      |
         (G3OP_MTX_LOAD_4x3 << 16)),
        1,
        GX_MTXMODE_POSITION_VECTOR,
        FX32_ONE, 0, 0, 
        0, FX32_ONE, 0,
        0, 0, FX32_ONE,
        0, 0, 0   // This is subject to change  (Trans)
    };
    
    VecFx32* trans = (VecFx32*)&bbcmd1[12];
    MtxFx44 m;

    NNS_G3D_NULL_ASSERT(rs);

    //
    // there is no callback point
    //

    //
    // cannot transfer to and from with matrix stack
    //
#pragma unused(opt)
    NNS_G3D_ASSERT(opt == NNS_G3D_SBCFLG_000);

    //
    // NNS_G3dGlbFlushAlt is prohibited
    //
    NNS_G3D_ASSERT(!(NNS_G3dGlb.flag & NNS_G3D_GLB_FLAG_FLUSH_ALT));

    //
    // NNS_G3D_RSFLAG_OPT_NOGECMD also prohibited
    //
    NNS_G3D_ASSERT(!(rs->flag & NNS_G3D_RSFLAG_OPT_NOGECMD));

    // flash the buffer
    NNS_G3dGeFlushBuffer();

    // command  transmission :
    // change to PROJ mode
    // save the projection matrix
    // set the unit matrix
    reg_G3X_GXFIFO = ((G3OP_MTX_MODE << 0) |
                      (G3OP_MTX_PUSH << 8) |
                      (G3OP_MTX_IDENTITY << 16));
    reg_G3X_GXFIFO = (u32)GX_MTXMODE_PROJECTION;

    // Wait further for the geometry engine to stop
    // get the current matrix
    while (G3X_GetClipMtx(&m))
        ;

    // billboard matrix calculation
    trans->x = m._30;
    trans->y = m._31;
    trans->z = m._32;

    // projection matrix POP
    // return to POS_VEC
    // store in the current matrix
    MI_CpuSend32(&bbcmd1[0],
                 &reg_G3X_GXFIFO,
                 15 * sizeof(u32));

    rs->c += 2;
}


/*---------------------------------------------------------------------------*
    MySbcBBYSimple
    
    Simplified Y-axis billboard processing function. High speed, but has the following limitations.
    - No callback point.
    - opt must be NNS_G3D_SBCFLG_000.
    - No support for other than NNS_G3dGlbFlushP.
    - No support for NNS_G3D_RSFLAG_OPT_NOGECMD.
    - Does not display correctly when scaling is applied.
 *---------------------------------------------------------------------------*/
static void
MySbcBBYSimple(NNSG3dRS* rs, u32 opt)
{
    MtxFx44 m;
    VecFx32 lz;

    static u32 bbcmd1[] =
    {
        ((G3OP_MTX_POP << 0)       |
         (G3OP_MTX_MODE << 8)      |
         (G3OP_MTX_LOAD_4x3 << 16)),
        1,
        GX_MTXMODE_POSITION_VECTOR,
        FX32_ONE, 0, 0, // This is subject to change  (4x3Mtx)
        0, FX32_ONE, 0,
        0, 0, FX32_ONE,
        0, 0, 0
    };
    VecFx32* trans = (VecFx32*)&bbcmd1[12];
    MtxFx43* mtx   = (MtxFx43*)&bbcmd1[3];

    NNS_G3D_NULL_ASSERT(rs);
    
    //
    // there is no callback point
    //

    //
    // cannot transfer to and from with matrix stack
    //
#pragma unused(opt)
    NNS_G3D_ASSERT(opt == NNS_G3D_SBCFLG_000);

    //
    // NNS_G3dGlbFlushAlt is prohibited
    //
    NNS_G3D_ASSERT(!(NNS_G3dGlb.flag & NNS_G3D_GLB_FLAG_FLUSH_ALT));

    //
    // NNS_G3D_RSFLAG_OPT_NOGECMD also prohibited
    //
    NNS_G3D_ASSERT(!(rs->flag & NNS_G3D_RSFLAG_OPT_NOGECMD));

    // flash the buffer
    NNS_G3dGeFlushBuffer();

    // command  transmission :
    // change to PROJ mode
    // save the projection matrix
    // set the unit matrix
    reg_G3X_GXFIFO = ((G3OP_MTX_MODE << 0) |
                      (G3OP_MTX_PUSH << 8) |
                      (G3OP_MTX_IDENTITY << 16));
    reg_G3X_GXFIFO = (u32)GX_MTXMODE_PROJECTION;

    // Wait further for the geometry engine to stop
    // get the current matrix (clip matrix)
    while (G3X_GetClipMtx(&m))
        ;

    // billboard matrix calculation
    trans->x = m._30;
    trans->y = m._31;
    trans->z = m._32;

    // mtx _00, _01, and _02 are unchangeable
    VEC_Normalize((VecFx32*)&m._10, (VecFx32*)&mtx->_10);

    lz.x = 0;
    lz.y = -m._12;
    lz.z = m._11;
    VEC_Normalize(&lz, (VecFx32*)&mtx->_20);

    // projection matrix POP
    // return to POS_VEC
    // store in the current matrix
    MI_CpuSend32(&bbcmd1[0],
                 &reg_G3X_GXFIFO,
                 15 * sizeof(u32));

    rs->c += 2;
}


/* -------------------------------------------------------------------------
  Name:         InitG3DLib

  Description:  Initializes G3D library.

  Arguments:    None.

  Returns:      None.
   ------------------------------------------------------------------------- */
static void
InitG3DLib(void)
{
    //
    // G3D:
    // performs default initialization
    // ------------------------------------------------------------------------
    NNS_G3dInit();
}


/* -------------------------------------------------------------------------
  Name:         LoadG3DModel

  Description:  Reads model data from file.

  Arguments:    path: Model data path name.

  Returns:      Pointer to model.
   ------------------------------------------------------------------------- */
static NNSG3dResMdl*
LoadG3DModel(const char* path)
{
    NNSG3dResMdl*        model   = NULL;
    NNSG3dResTex*        texture = NULL;
    NNSG3dResFileHeader* resFile = G3DDemo_LoadFile(path);
    BOOL status;
    NNS_G3D_NULL_ASSERT(resFile);

    // calls the default initialization function and sets up
    status = NNS_G3dResDefaultSetup(resFile);
    NNS_G3D_ASSERTMSG(status, "NNS_G3dResDefaultSetup failed");
    
    //
    // G3D: get model
    // nsbmd can include multiple models, so sets the index (0 when there is one model)
    // and gets the pointer to the one model.
    // --------------------------------------------------------------------
    model = NNS_G3dGetMdlByIdx(NNS_G3dGetMdlSet(resFile), 0);
    return model;
}


/* -------------------------------------------------------------------------
  Name:         SetCamera

  Description:  Sets camera matrix and projection matrix in G3D global state.

  Arguments:    camera:     pointer to the G3DDemoCamera structure

  Returns:      None.
   ------------------------------------------------------------------------- */
static void
SetCamera(G3DDemoCamera* camera)
{
    G3DDemoLookAt* lookat = &camera->lookat;
    G3DDemoPersp*  persp  = &camera->persp;

    NNS_G3dGlbPerspective(persp->fovySin, persp->fovyCos, persp->aspect, persp->nearClip, persp->farClip);
    NNS_G3dGlbLookAt(&lookat->camPos, &lookat->camUp, &lookat->target);
}

