/*---------------------------------------------------------------------------*
  Project:	NITRO-System - build - demos - g3d - samples - ShadowVolume
  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:11:22  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  11/04/2004 10:29:11  nishida
  Changed demo name displayed on screen to ShadowVolume.

  Revision 1.1  09/28/2004 02:22:53  nishida_kenji
  Initial check in.

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

//---------------------------------------------------------------------------
//
// Shadow Volume display sample
//
// The following is an example of a method of rendering the shadow volume using G3D.
//
// Sample Operation
// The visibility animation uses the shadow volume to apply a shadow to
// a repeatedly-playing object.
//
// HOWTO:
// 1: Confirm that manual sort mode has been designated with G3_SwapBuffers.
// 2: Set the same model to the shadow olygon material for the mask and render.
//    Afterwards, set to the material for shadow polygon rendering and render.
//    For further details, see the comments in the DrawShadowVolume function in the sample.
//
//---------------------------------------------------------------------------

#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);


/* -------------------------------------------------------------------------
    DrawShadowVolume

    Renders the Shadow Volume.
 ------------------------------------------------------------------------- */
static void
DrawShadowVolume(NNSG3dResMdl* p, BOOL isShadow)
{
    //
    // Shadow Volume rendering
    //
    // To begin with, if rendering even one shadow volume, you must
    // use manual sort mode.
    // In other words,
    // Either use G3_SwapBuffers(GX_SORTMODE_MANUAL, GX_BUFFERMODE_W) or
    // G3_SwapBuffers(GX_SORTMODE_MANUAL, GX_BUFFERMODE_Z).
    //
    // If rendering one model by setting a shadow polygon material for the mask and then next rendering
    // the same model by setting a shadow polygon material for rendering,
    // a shadow can be cast on the object.
    // However, the model used for the shadow volume must go through the object that casts the shadow.
    //
    {
        // Position adjustment
        VecFx32 scale = {FX32_ONE, FX32_ONE, FX32_ONE};
        VecFx32 trans = {0, 5 * FX32_ONE, 0};
        NNS_G3dGlbSetBaseScale(&scale);
        NNS_G3dGlbSetBaseTrans(&trans);
        NNS_G3dGlbFlushP();
        trans.y = 0;
        NNS_G3dGlbSetBaseTrans(&trans);
    }

    if (isShadow)
    {
        {
            //
            // Renders the shadow polygon for the mask.
            // Sets polygon ID to 0,  alpha to 1-30,  and polygon mode to GX_POLYGONMODE_SHADOW
            // and only renders the rear surfaces.(GX_CULL_FRONT)
            //
            NNS_G3dMdlSetMdlLightEnableFlag( p, 0, 0 );
            NNS_G3dMdlSetMdlPolygonID( p, 0, 0 );                       // set the polygon ID to 0
            NNS_G3dMdlSetMdlCullMode( p, 0, GX_CULL_FRONT );            // Render only the back surfaces
            NNS_G3dMdlSetMdlAlpha( p, 0, 10 );
            NNS_G3dMdlSetMdlPolygonMode( p, 0, GX_POLYGONMODE_SHADOW ); // use shadow polygon

            // render matID=0, shpID=0(do not omit the material sending)
            NNS_G3dDraw1Mat1Shp(p, 0, 0, TRUE);
        }

        {
            //
            // Renders the shadow polygon for rendering in the same location.
            // Sets polygon ID to 1-63,  alpha to 1-30,  and polygon mode to GX_POLYGONMODE_SHADOW
            // and renders both surfaces. (GX_CULL_NONE)
            //
            // If set to GX_CULL_BACK, there are cases where the shadow polygon for the mask will stick out
            // and it won't render properly.
            //
            // Make the polygon ID different from that of the object that displays the shadow.
            // If the polygon IDs are the same, the shadow will not be rendered. If you do not want the object that casts the shadow
            // to display the shadow, make the polygon IDs of the object that casts the shadow and the shadow polygon
            // for rendering the same.
            //
            NNS_G3dMdlSetMdlLightEnableFlag( p, 0, 0 );
            NNS_G3dMdlSetMdlPolygonID( p, 0, 1 );                       // sets polygon ID to 1-63
            NNS_G3dMdlSetMdlCullMode( p, 0, GX_CULL_NONE );             // render both surfaces
            NNS_G3dMdlSetMdlAlpha( p, 0, 10 );
            NNS_G3dMdlSetMdlPolygonMode( p, 0, GX_POLYGONMODE_SHADOW ); // use shadow polygon

            // render matID=0, shpID=0(do not omit the material sending)
            NNS_G3dDraw1Mat1Shp(p, 0, 0, TRUE);
        }
    }
    else
    {
        //
        // Render not as a shadow polygon but as a normal semi-transparent polygon.
        // The model being used in this sample is cylindrical.
        // 
        NNS_G3dMdlSetMdlLightEnableFlag( p, 0, 0 );
        NNS_G3dMdlSetMdlPolygonID( p, 0, 0 );
        NNS_G3dMdlSetMdlCullMode( p, 0, GX_CULL_BACK ); 
        NNS_G3dMdlSetMdlAlpha( p, 0, 10 );
        NNS_G3dMdlSetMdlPolygonMode( p, 0, GX_POLYGONMODE_MODULATE );

        // render matID=0, shpID=0(do not omit the material sending)
        NNS_G3dDraw1Mat1Shp(p, 0, 0, TRUE);
    }
}


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

  Description:  Sample main.

  Arguments:    None.

  Returns:      None.
   ------------------------------------------------------------------------- */
void
NitroMain(void)
{
    NNSFndAllocator allocator;
    
    NNSG3dRenderObj object;
    NNSG3dResMdl*   pModel;
    void*           pAnmRes;
    NNSG3dAnmObj*   pAnmObj;
    BOOL isShadow = TRUE;

    //
    // Since the Shadow Volume is normally made up of one material and one shape,
    // it can be rendered with NNS_G3dDraw1Mat1Shp. Accordingly, since NNSG3dRenderObj is not needed,
    // it is sufficient to set up only NNSG3dResMdl.
    //
    NNSG3dResMdl*   pShadow;

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

    InitG3DLib();
    G3DDemo_InitDisplay();

    //
    // create allocator with 4-byte alignment
    //
    NNS_FndInitAllocatorForExpHeap(&allocator, G3DDemo_AppHeap, 4);

    // 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);

    pModel = LoadG3DModel("data/brother.nsbmd");
    SDK_ASSERTMSG(pModel, "load failed");

    pShadow = LoadG3DModel("data/shadow.nsbmd");
    SDK_ASSERTMSG(pShadow, "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, pModel);
    
    //
    // Read visibility animation and associate.
    //
    {
        const u8* pFile = G3DDemo_LoadFile("data/brother.nsbva");
        SDK_ASSERTMSG( pFile, "anm load failed" );
        {
            //
            // initialize animation, add to rendering object
            // 
            // Allocate and initialize the animation object (NNSG3dAnmObj) that goes between
            // the loaded animation file and NNSG3dRenderObj.
            // All animation is set up with the same code.
            //

            //
            // Allocates from the allocator the memory necessary for the animation object.
            // You must specify and allocate model resources and animation resources.
            //

            // specify index #0 animation
            pAnmRes = NNS_G3dGetAnmByIdx(pFile, 0);
            SDK_NULL_ASSERT(pAnmRes);

            // Allocates the necessary amount of memory. Initialize must be separate.
            pAnmObj = NNS_G3dAllocAnmObj(&allocator, // specify the allocator to use
                                         pAnmRes,    // specify animation resource
                                         pModel);    // specify model resource
            SDK_NULL_ASSERT(pAnmObj);

            //
            // Initialize AnmObj.
            //
            NNS_G3dAnmObjInit(pAnmObj, // pointer to animation object
                              pAnmRes, // pointer to animation resource
                              pModel,  // pointer to NNSG3dResMdl
                              NULL );  // pointer to NNSG3dResTex (NULL is OK except for texture pattern animation)

            //
            // Add AnmObj to RenderObj.
            //
            NNS_G3dRenderObjAddAnmObj( &object, pAnmObj );
        }
    }
    
    //
    // demo common initialization
    //
    G3DDemo_InitCamera(&gCamera, 2*FX32_ONE, 16*FX32_ONE);
    G3DDemo_InitGround(&gGround, (fx32)(1.5*FX32_ONE));

    G3DDemo_InitConsole();
    G3DDemo_Print(0,0, G3DDEMO_COLOR_YELLOW, "ShadowVolume");

    for(;;)
    {
        OSTick time;

        SVC_WaitVBlankIntr();
        G3DDemo_PrintApplyToHW();
        G3DDemo_ReadGamePad();

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

        G3X_Reset();
        G3X_ResetMtxStack();

        SetCamera(&gCamera);

        // Update animation frame.
        pAnmObj->frame += FX32_ONE;

        if (pAnmObj->frame >= NNS_G3dAnmObjGetNumFrame(pAnmObj))
        {
            pAnmObj->frame = 0;
        }

        if (G3DDEMO_IS_PAD_TRIGGER(PAD_BUTTON_A))
        {
            isShadow = isShadow ? FALSE : TRUE;
        }

        time = OS_GetTick();
        {
            {
                // apply scaling and enlarge
                VecFx32 scale = {FX32_ONE << 1, FX32_ONE << 1 , FX32_ONE << 1};
                NNS_G3dGlbSetBaseScale(&scale);

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

                //
                // G3D: NNS_G3dDraw
                // Performs drawing for all instances by passing the
                // NNSG3dRenderObj that was set up.
                //
                NNS_G3dDraw(&object);
            }

            DrawShadowVolume(pShadow, isShadow);
        }
        time = OS_GetTick() - time;

        {
            //
            // G3D: Before calling normal G3_XXX, NNS_G3dGeComFlushBuffer()
            //      must be called and synchronized
            //
            NNS_G3dGlbFlushP();
            NNS_G3dGeFlushBuffer();
            G3DDemo_DrawGround(&gGround);
        }

        //
        // NOTICE:
        // When using Shadow Polygons, you must use manual sort mode.
        // If auto sort is used, the user cannot set the rendering order for semi-transparent polygons,
        // and the Shadow Volume cannot be correctly rendered.
        //
        G3_SwapBuffers(GX_SORTMODE_MANUAL, GX_BUFFERMODE_W);

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


/* -------------------------------------------------------------------------
  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;
    SDK_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);
}

