/*---------------------------------------------------------------------------*
  Project:  NITRO-System - build - demos - g3d - samples - callback5
  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.7  02/25/2005 03:09:17  takano_makoto
  Warning measures for signed<->unsigned casts

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

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

  Revision 1.4  10/21/2004 02:17:05  nishida_kenji
  Use NNS_G3dRSGetCurrentNodeDescID.

  Revision 1.3  10/20/2004 01:39:27  nishida_kenji
  Use accessors for NNSG3dRS and NNSG3dRenderObj.

  Revision 1.2  10/13/2004 06:15:02  nishida_kenji
  fix comments.

  Revision 1.1  09/27/2004 10:52:55  nishida_kenji
  Initial check in.

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

//---------------------------------------------------------------------------
// Sample that customizes G3D drawing operations using callback (Part 5)
//
// The function NNS_G3dRenderObjSetInitFunc can be used to call a user-defined function before rendering.
// It is possible code to processing within this function to allow registration of multiple callback functions.
//
// Sample Operation
// Multiple callback functions are used to customize the rendering of the robot.
// When the A Button is pressed, the same series of processes get executed that
//                     in the callback1 sample involved the pressing of the A, X & Y buttons.
//
// HOWTO:
// 1: Use the function NNS_G3dRenderObjSetInitFunc to register functions that execute before rendering for NSG3dRenderObj.
//     
// 2: Inside the registered function, proceed to register multiple necessary callbacks for the NNSG3dRS structure using the NNS_G3dRSSetCallBack function.
//     
//
//---------------------------------------------------------------------------


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




/* -------------------------------------------------------------------------
    eraseWaist

    This callback is put in the SBC's NODE command (the NNSi_G3dFuncSbc_NODE function).
    By using NODE commands register callbacks with timing A, timing B you can
    delete or change the visibility calculation results.
    Can be referenced from rs->pVisAnmResult.

    When called, will not display if the rendering node name is "chest."
 ------------------------------------------------------------------------- */
static void
eraseWaist(NNSG3dRS* rs)
{
    int jntID;
    NNS_G3D_GET_JNTID(NNS_G3dRenderObjGetResMdl(NNS_G3dRSGetRenderObj(rs)),
                      &jntID,
                      "chest");
    SDK_ASSERT(jntID >= 0);

    if (NNS_G3dRSGetCurrentNodeID(rs) == jntID)
    {
        NNSG3dVisAnmResult* visResult = NNS_G3dRSGetVisAnmResult(rs);
        visResult->isVisible = FALSE;
        //
        // If the callback does not need to be used for anything other than
        // the above, the impact on performance can be reduced somewhat by resetting.
        //
        NNS_G3dRSResetCallBack(rs, NNS_G3D_SBC_NODE);
    }
}


/* -------------------------------------------------------------------------
    getTranslucent

    This callback is put in the SBC's MAT command (the NNSi_G3dFuncSbc_MAT function).
    By using MAT commands to register callbacks with timing A, timing B you can
    delete or change the material calculation results.
    Can be referenced from rs->pMatAnmResult.

    Displays as translucent if the render material name is "lambert2," otherwise
    displays as wireframe.
 ------------------------------------------------------------------------- */
static void
getTranslucent(NNSG3dRS* rs)
{
    int matID;
    NNSG3dMatAnmResult* matResult;
    NNS_G3D_GET_MATID(NNS_G3dRenderObjGetResMdl(NNS_G3dRSGetRenderObj(rs)),
                      &matID,
                      "lambert2");
    SDK_ASSERT(matID >= 0);

    matResult = NNS_G3dRSGetMatAnmResult(rs);

    if (NNS_G3dRSGetCurrentMatID(rs) == matID)
    {
        matResult->prmPolygonAttr =
            (matResult->prmPolygonAttr & ~REG_G3_POLYGON_ATTR_ALPHA_MASK) |
            (16 << REG_G3_POLYGON_ATTR_ALPHA_SHIFT);
    }
    else
    {
        matResult->flag |= NNS_G3D_MATANM_RESULTFLAG_WIREFRAME;
    }
}


/* -------------------------------------------------------------------------
    scaleArm

    This callback is put in the SBC's NODEDESC command (the NNSi_G3dFuncSbc_NODEDESC function).
    With the NODEDESC command, you can create and change the joint calculation results
    by registering the callback in timing A and timing B.
    Can be referenced from rs-> pJntAnmResult.

    The first argument of the NODEDESC command is the joint ID calculated by the NODEDESC command.
    The scale is 2x only when calculating left_upper_arm.
 ------------------------------------------------------------------------- */
static void
scaleArm(NNSG3dRS* rs)
{
    int jntID;
    
    NNS_G3D_GET_JNTID(NNS_G3dRenderObjGetResMdl(NNS_G3dRSGetRenderObj(rs)),
                      &jntID,
                      "left_upper_arm");
    SDK_ASSERT(jntID >= 0);

    if (NNS_G3dRSGetCurrentNodeDescID(rs) == jntID)
    {
        NNSG3dJntAnmResult* jntResult;
        jntResult = NNS_G3dRSGetJntAnmResult(rs);

        jntResult->flag &= ~NNS_G3D_JNTANM_RESULTFLAG_SCALE_ONE;
        jntResult->scale.x = 2 * FX32_ONE;
        jntResult->scale.y = 2 * FX32_ONE;
        jntResult->scale.z = 2 * FX32_ONE;

        //
        // If the callback does not need to be used for anything other than
        // the above, the impact on performance can be reduced somewhat by resetting.
        //
        NNS_G3dRSResetCallBack(rs, NNS_G3D_SBC_NODEDESC);
    }
}


static void
setCallback(NNSG3dRS* rs)
{
    NNS_G3dRSSetCallBack(rs, &scaleArm, NNS_G3D_SBC_NODEDESC, NNS_G3D_SBC_CALLBACK_TIMING_B);
    NNS_G3dRSSetCallBack(rs, &getTranslucent, NNS_G3D_SBC_MAT, NNS_G3D_SBC_CALLBACK_TIMING_B);
    NNS_G3dRSSetCallBack(rs, &eraseWaist, NNS_G3D_SBC_NODE, NNS_G3D_SBC_CALLBACK_TIMING_B);
}

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

  Description:  Sample main.

  Arguments:    None.

  Returns:      None.
   ------------------------------------------------------------------------- */
void
NitroMain(void)
{
    NNSG3dRenderObj object;
    NNSG3dResMdl*    model;

    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/robot_t.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, 5*FX32_ONE);
    G3DDemo_InitGround(&gGround, (fx32)(1.5*FX32_ONE));

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

    for(;;)
    {
        OSTick time;

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

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

        G3X_Reset();
        G3X_ResetMtxStack();

        SetCamera(&gCamera);

        {
            VecFx32 scale = {FX32_ONE>>2, FX32_ONE>>2, FX32_ONE>>2};
            NNS_G3dGlbSetBaseScale(&scale);

            //
            // Set the callback funtion that is set by the button that is pressed,
            // the SBC command that enters the callback, and the command's internal timing.
            //
            if (G3DDEMO_IS_PAD_PRESS(PAD_BUTTON_A))
            {
                NNS_G3dRenderObjSetInitFunc(&object, &setCallback);
            }
            else
            {
                // Reset callback settings if no button gets pressed
                NNS_G3dRenderObjSetInitFunc(&object, NULL);
            }

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


            time = OS_GetTick();

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

            // Time measurement.
            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_G3dGlbFlush();
            NNS_G3dGeFlushBuffer();
            G3DDemo_DrawGround(&gGround);
        }

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


        G3DDemo_Printf(16,0, G3DDEMO_COLOR_YELLOW, "TIME:%06ld 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);
}

