/*---------------------------------------------------------------------------*
  Project:   NITRO-System - build - demos - g2d - samples - Renderer_CallBack2
  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.4  06/29/2005 05:59:56  kitani_toshikazu
  Cleaned up source code and added comments.

  Revision 1.3  05/31/2005 05:27:31  kitani_toshikazu
  Supported build in ProDG environment.

  Revision 1.2  11/15/2004 05:55:32  kitani_toshikazu
  Revised comments, revised debug messages

  Revision 1.1  11/10/2004 04:44:32  kitani_toshikazu
  Registered initial issue

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

// ============================================================================
//  Explanation of the demo:
//      This sample carries out visible culling for OBJ units using the callback
//      before rendereer module OBJ drawing.
//      
//      The cell of a large pencil covering two screens is displayed.
//      The sample demo provides a debug display of the time required for processing by NNS_G2dDrawCellAnimation().
//
//  Operation Method
//      START Button culling ON,OFF
//      X Button Resets the cell and surface positions.
//      +Control Pad: Moves the cell.
//      Y + Control Pad Moves the main surface.
//      A Turns ON/OFF the cell rotation.
//
// ============================================================================

#include <nitro.h>
#include <nnsys/g2d.h>

#include "g2d_demolib.h"


//------------------------------------------------------------------------------
// Constant definitions
#define SCREEN_WIDTH        256                 // screen width
#define SCREEN_HEIGHT       192                 // screen height

#define NUM_OF_OAM          128                 // Number of OAMs allocated to OAM manager
#define NUM_OF_AFFINE       (NUM_OF_OAM / 4)    // Number of affine parameters allocated to OAM Manager 

#define CHARA_BASE          0x0                 // Character image base address
#define PLTT_BASE           0x0                 // Palette base address

#define INIT_OUTPUT_TYPE    NNS_G2D_SURFACETYPE_MAIN2D   // Initial value for output method

// Initial position of surface
#define SURFACE_MAIN_INIT_POS ((NNSG2dFVec2){0 << FX32_SHIFT, 0 << FX32_SHIFT})             
// Initial position of surface
#define SURFACE_SUB_INIT_POS ((NNSG2dFVec2){0 << FX32_SHIFT, SCREEN_HEIGHT << FX32_SHIFT})  
// Initial position of cell
#define CELL_INIT_POS ((NNSG2dFVec2){150 << FX32_SHIFT, 120 << FX32_SHIFT})   



//------------------------------------------------------------------------------
// structure definitions


//------------------------------------------------------------------------------
// Global Variables
static NNSG2dImageProxy             sImageProxy;    // Character/texture proxy for cell
static NNSG2dImagePaletteProxy      sPaletteProxy;  // Palette proxy for cell

static NNSG2dOamManagerInstance     sMainOamMan;    // OAM Manager for main screen
static NNSG2dOamManagerInstance     sSubOamMan;     // OAM Manager for subscreen

static NNSG2dAnimBankData*          spAnimBank;     // Animation Bank
static NNSG2dCellAnimation*         spCellAnim;     // Cell animation

//------------------------------------------------------------------------------
// Prototype declarations



/*---------------------------------------------------------------------------*
  Name:         CallBackAddOam*

  Description:  Function called internally by NNS_G2dDraw* functions to add an OAM .
                It passes OAMs to the OAM Manager.

  Arguments:    pOam :           Pointer to OAM to be added
                affineIndex :    Affine index used by this OAM.
                                May be a value of 32 or larger.
                                If affine is not used,
                                NNS_G2D_OAM_AFFINE_IDX_NONE is specified.
                bDoubleAffine :  Whether in double affine mode.

  Returns:      Returns TRUE if successful; returns FALSE otherwise.
 *---------------------------------------------------------------------------*/
// For main screen
static BOOL CallBackAddOamMain( const GXOamAttr* pOam, u16 affineIndex, BOOL bDoubleAffine )
{
#pragma unused( bDoubleAffine )
    SDK_NULL_ASSERT( pOam );

    return NNS_G2dEntryOamManagerOamWithAffineIdx( &sMainOamMan, pOam, affineIndex );
}

// For subscreen
static BOOL CallBackAddOamSub( const GXOamAttr* pOam, u16 affineIndex, BOOL bDoubleAffine )
{
#pragma unused( bDoubleAffine )
    SDK_NULL_ASSERT( pOam );

    return NNS_G2dEntryOamManagerOamWithAffineIdx( &sSubOamMan, pOam, affineIndex );
}

/*---------------------------------------------------------------------------*
  Name:         CallBackAddAffine*

  Description:  This function is called to add affine parameters in the
                NNS_G2dDraw* functions.
                It passes affine parameters to the  OAM Manager.

  Arguments:    mtx :        Pointer to affine transform matrix to be added.

  Returns:      Returns the AffineIndex for referencing the affine parameters
                added.
 *---------------------------------------------------------------------------*/
// For main screen
static u16 CallBackAddAffineMain( const MtxFx22* mtx )
{
    SDK_NULL_ASSERT( mtx );
    return NNS_G2dEntryOamManagerAffine( &sMainOamMan, mtx );
}

// For subscreen
static u16 CallBackAddAffineSub( const MtxFx22* mtx )
{
    SDK_NULL_ASSERT( mtx );
    return NNS_G2dEntryOamManagerAffine( &sSubOamMan, mtx );
}

//------------------------------------------------------------------------------
// Determines if rectangle is in view.
static BOOL IsRectInView_
( 
    const fx32  px,
    const fx32  py,
    const fx32 _min_x, 
    const fx32 _max_x,
    const fx32 _min_y,
    const fx32 _max_y,
    const MtxFx32*          pMtx,
    const NNSG2dViewRect*   pViewRect
)
{
        s32 max_x, min_x;
    s32 max_y, min_y;
    s32 temp;
    
    // Requests maximum/minimum XY display coordinates after scaling conversion
    min_x = FX_Mul(_min_x, pMtx->_00) + FX_Mul(_min_y, pMtx->_10);  // upper left
    max_x = min_x;
    
    temp  = FX_Mul(_min_x, pMtx->_00) + FX_Mul(_max_y, pMtx->_10);  // lower left
    if      (temp < min_x) { min_x = temp; }
    else if (temp > max_x) { max_x = temp; }
    temp  = FX_Mul(_max_x, pMtx->_00) + FX_Mul(_min_y, pMtx->_10);  // upper right
    if      (temp < min_x) { min_x = temp; }
    else if (temp > max_x) { max_x = temp; }
    temp  = FX_Mul(_max_x, pMtx->_00) + FX_Mul(_max_y, pMtx->_10);  // lower right
    if      (temp < min_x) { min_x = temp; }
    else if (temp > max_x) { max_x = temp; }
    
    min_y = FX_Mul(_min_x, pMtx->_01) + FX_Mul(_min_y, pMtx->_11);  // upper left
    max_y = min_y;
    
    temp  = FX_Mul(_min_x, pMtx->_01) + FX_Mul(_max_y, pMtx->_11);  // lower left
    if      (temp < min_y) { min_y = temp; }
    else if (temp > max_y) { max_y = temp; }
    
    temp  = FX_Mul(_max_x, pMtx->_01) + FX_Mul(_min_y, pMtx->_11);  // upper right
    if      (temp < min_y) { min_y = temp; }
    else if (temp > max_y) { max_y = temp; }
    
    temp  = FX_Mul(_max_x, pMtx->_01) + FX_Mul(_max_y, pMtx->_11);  // lower right
    if      (temp < min_y) { min_y = temp; }
    else if (temp > max_y) { max_y = temp; }
    
    // Adds the display position origin of the cell
    min_x  += px;
    max_x  += px;
    min_y  += py;
    max_y  += py;
    
    // Determines inside/outside of surface
    if( (max_y > 0) && (min_y < pViewRect->sizeView.y) )
    {
        if( (max_x > 0) && (min_x < pViewRect->sizeView.x) )
        {
            return TRUE;
        }
    }
    return FALSE;
}
//------------------------------------------------------------------------------
// Implements the OBJ culling function as the callback prior to OBJ drawing.
// There are many OBJs when comparing with cells so you need to be aware of the
// large increase in process load due to culling.
static void OBJCullingFunc_
(
    struct NNSG2dRendererInstance*         pRend,
    struct NNSG2dRenderSurface*            pSurface,
    const  NNSG2dCellData*                 pCell,
    u16                                    oamIdx,
    const MtxFx32*                         pMtx
)
{
#pragma unused( pCell )
#pragma unused( oamIdx )
    {
        const GXOamAttr* pOam = &pRend->rendererCore.currentOam;
        const GXOamShape shape = NNS_G2dGetOAMSize( pOam ); 
        
        const fx32 minX = NNS_G2dRepeatXinCellSpace( (s16)pOam->x ) * FX32_ONE ;
        const fx32 maxX = ( NNS_G2dRepeatXinCellSpace( (s16)pOam->x ) + NNS_G2dGetOamSizeX( &shape ) ) * FX32_ONE;
        const fx32 minY = NNS_G2dRepeatYinCellSpace( pOam->y ) * FX32_ONE;
        const fx32 maxY = ( NNS_G2dRepeatYinCellSpace( pOam->y ) + NNS_G2dGetOamSizeY( &shape ) ) * FX32_ONE;
        
        const fx32  px = pMtx->_20 - pSurface->viewRect.posTopLeft.x;
        const fx32  py = pMtx->_21 - pSurface->viewRect.posTopLeft.y;
                    
        if( IsRectInView_( px, py, minX, maxX, minY, maxY, pMtx, &pSurface->viewRect ) )
        {
            pRend->rendererCore.bDrawEnable = TRUE;
        }else{
            pRend->rendererCore.bDrawEnable = FALSE;
        }
    }   
}
/*---------------------------------------------------------------------------*
  Name:         InitOamManager

  Description:  This function carries out the initialization of the OAM Manager.

  Arguments:    pOamManager:    Pointer to the OAM manager to be initialized.
                type    :           Specifies the graphics engine the initialization
                                is carried out for.

  Returns:      None.
 *---------------------------------------------------------------------------*/
static void InitOamManager(NNSG2dOamManagerInstance* pOamManager, NNSG2dOamType type)
{
    BOOL result;
    SDK_NULL_ASSERT( pOamManager );

    result = NNS_G2dGetNewManagerInstance( pOamManager, 0, NUM_OF_OAM - 1, type );
    SDK_ASSERT( result );
    result = NNS_G2dInitManagerInstanceAffine( pOamManager, 0, NUM_OF_AFFINE - 1 );
    SDK_ASSERT( result );
}

/*---------------------------------------------------------------------------*
  Name:         InitRenderer

  Description:  Initializes Renderer and Surface.

  Arguments:    pRenderer:      Pointer to Renderer to be initialized.
                pMainSurface:   Pointer to the main screen surface initialized.
                pSubSurface:    Pointer to the subscreen surface initialized.

  Returns:      None.
 *---------------------------------------------------------------------------*/
static void InitRenderer(
                NNSG2dRendererInstance* pRenderer,
                NNSG2dRenderSurface*    pMainSurface,
                NNSG2dRenderSurface*    pSubSurface)
{
    SDK_NULL_ASSERT( pRenderer );
    SDK_NULL_ASSERT( pMainSurface );
    SDK_NULL_ASSERT( pSubSurface );

    NNS_G2dInitRenderSurface( pMainSurface );
    NNS_G2dInitRenderSurface( pSubSurface );

    // Initializes the main screen surface.
    {
        NNSG2dViewRect* pRect = &(pMainSurface->viewRect);

        // Display rectangle
        pRect->posTopLeft = SURFACE_MAIN_INIT_POS;
        pRect->sizeView.x = FX32_ONE * SCREEN_WIDTH;
        pRect->sizeView.y = FX32_ONE * SCREEN_HEIGHT;

        // Callback function
        pMainSurface->pFuncOamRegister          = CallBackAddOamMain;
        pMainSurface->pFuncOamAffineRegister    = CallBackAddAffineMain;
        
        // display destination
        pMainSurface->type                      = NNS_G2D_SURFACETYPE_MAIN2D;
        
        pMainSurface->pBeforeDrawOamBackFunc = OBJCullingFunc_;
    }

    // Initializes the subscreen surface
    {
        NNSG2dViewRect* pRect = &(pSubSurface->viewRect);

        // Display rectangle
        pRect->posTopLeft = SURFACE_SUB_INIT_POS;
        pRect->sizeView.x = FX32_ONE * SCREEN_WIDTH;
        pRect->sizeView.y = FX32_ONE * SCREEN_HEIGHT;

        // Callback function
        pSubSurface->pFuncOamRegister           = CallBackAddOamSub;
        pSubSurface->pFuncOamAffineRegister     = CallBackAddAffineSub;
        
        // display destination
        pSubSurface->type                       = NNS_G2D_SURFACETYPE_SUB2D;
        
        pSubSurface->pBeforeDrawOamBackFunc = OBJCullingFunc_;
    }
    
    
    // Initializes the renderer.
    {
        NNS_G2dInitRenderer( pRenderer );

        // Surface registration
        NNS_G2dAddRendererTargetSurface( pRenderer, pMainSurface );
        NNS_G2dAddRendererTargetSurface( pRenderer, pSubSurface );

        // Proxy registration
        NNS_G2dSetRendererImageProxy( pRenderer, &sImageProxy, &sPaletteProxy );

        // Specify the Z offset increment
        NNS_G2dSetRendererSpriteZoffset( pRenderer, - FX32_ONE );
    }
}

/*---------------------------------------------------------------------------*
  Name:         LoadResource

  Description:  Reads the animation data, cell bank, character data, and
                palette data from the file. It loads the character data and
                palette data to the VRAM.

  Arguments:    None.

  Returns:      Pointer to the cell bank that was read.
 *---------------------------------------------------------------------------*/
static NNSG2dCellDataBank* LoadResources(void)
{
    NNSG2dCellDataBank* pCellBank;
    void* pBuf;

    NNS_G2dInitImageProxy( &sImageProxy );
    NNS_G2dInitImagePaletteProxy( &sPaletteProxy );

    //------------------------------------------------------------------------------
    // load cell data
    {
        pBuf = G2DDemo_LoadNCER( &pCellBank, "data/Rdr_CallBack2.NCER" );
        SDK_NULL_ASSERT( pBuf );
        // Because the cell bank is used in main memory until the end,
        // this pBuf is not freed.
    }

    //------------------------------------------------------------------------------
    // load animation data
    {
        // Because the cell animation data are used in main memory until the end,
        // this pBuf is not freed.
        pBuf = G2DDemo_LoadNANR( &spAnimBank, "data/Rdr_CallBack2.NANR" );
        SDK_NULL_ASSERT( pBuf );

        //
        // Initialize cell animation entity
        //
        {
            spCellAnim = G2DDemo_GetNewCellAnimation(1);
            SDK_NULL_ASSERT( spCellAnim );

            SDK_NULL_ASSERT( NNS_G2dGetAnimSequenceByIdx(spAnimBank, 0) );

            NNS_G2dInitCellAnimation(
                spCellAnim,
                NNS_G2dGetAnimSequenceByIdx(spAnimBank, 0),
                pCellBank );
        }
    }

    //------------------------------------------------------------------------------
    // load character data for 2D
    {
        NNSG2dCharacterData* pCharData;

        pBuf = G2DDemo_LoadNCGR( &pCharData, "data/Rdr_CallBack2.NCGR" );
        SDK_NULL_ASSERT( pBuf );

        // Loading For Main 2D Graphics Engine.
        NNS_G2dLoadImage2DMapping(
            pCharData,
            CHARA_BASE,
            NNS_G2D_VRAM_TYPE_2DMAIN,
            &sImageProxy );

        // Loading For Sub 2D Graphics Engine.
        NNS_G2dLoadImage2DMapping(
            pCharData,
            CHARA_BASE,
            NNS_G2D_VRAM_TYPE_2DSUB,
            &sImageProxy );

        // Character data were copied into VRAM,
        // this pBuf is freed. Same below.
        G2DDemo_Free( pBuf );
    }

    //------------------------------------------------------------------------------
    // load palette data
    {
        NNSG2dPaletteData* pPlttData;

        pBuf = G2DDemo_LoadNCLR( &pPlttData, "data/Rdr_CallBack2.NCLR" );
        SDK_NULL_ASSERT( pBuf );
    
        // Loading For Main 2D Graphics Engine.
        NNS_G2dLoadPalette(
            pPlttData,
            PLTT_BASE,
            NNS_G2D_VRAM_TYPE_2DMAIN,
            &sPaletteProxy);
        // Loading For Sub 2D Graphics Engine.
        NNS_G2dLoadPalette(
            pPlttData,
            PLTT_BASE,
            NNS_G2D_VRAM_TYPE_2DSUB,
            &sPaletteProxy);
        G2DDemo_Free( pBuf );
    }

    return pCellBank;
}

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

  Description:  This is the main function.

  Arguments:    None.

  Returns:      None.
 *---------------------------------------------------------------------------*/
void NitroMain(void)
{
    NNSG2dRendererInstance  render;         // Renderer for rendering
    NNSG2dRenderSurface     mainSurface;    // Main screen surface
    NNSG2dRenderSurface     subSurface;     // subscreen surface
    NNSG2dCellDataBank*     pCellBank;      // Cell data


    // Initilizing App.
    {
        G2DDemo_CommonInit();
        G2DDemo_PrintInit();

        NNS_G2dInitOamManagerModule();
        InitOamManager( &sMainOamMan, NNS_G2D_OAMTYPE_MAIN );
        InitOamManager( &sSubOamMan, NNS_G2D_OAMTYPE_SUB );
        InitRenderer( &render, &mainSurface, &subSurface );
        pCellBank = LoadResources();
    }

    // start display
    {
        SVC_WaitVBlankIntr();
        GX_DispOn();
        GXS_DispOn();
    }

    // Main loop
    while( TRUE )
    {
        static NNSG2dFVec2 cellAPos = CELL_INIT_POS;  // Cell A drawing position
        static u16         priority = 0;
        static u16         plttNo   = 0;
        static BOOL        bRotate = FALSE;
        static u16         rot = 0x0;
        static OSTick      time;

        //------------------------------------------------------
        // Reads the pad input and updates the data.
        {
            int dx = 0;
            int dy = 0;
            
            G2DDemo_ReadGamePad();

            // Obtains the Control Pad input.
            {
                if ( G2DDEMO_IS_PRESS(PAD_KEY_UP) )
                {
                    dy--;
                }
                if ( G2DDEMO_IS_PRESS(PAD_KEY_DOWN) )
                {
                    dy++;
                }
                if ( G2DDEMO_IS_PRESS(PAD_KEY_LEFT) )
                {
                    dx--;
                }
                if ( G2DDEMO_IS_PRESS(PAD_KEY_RIGHT) )
                {
                    dx++;
                }
            }

            if( G2DDEMO_IS_PRESS(PAD_BUTTON_Y) )
            {
                // Moves main screen surface
                mainSurface.viewRect.posTopLeft.x += (dx << FX32_SHIFT);
                mainSurface.viewRect.posTopLeft.y += (dy << FX32_SHIFT);
            }
            else
            {
                // cell movement
                cellAPos.x += (dx << FX32_SHIFT);
                cellAPos.y += (dy << FX32_SHIFT);
            }

            // reset
            if( G2DDEMO_IS_TRIGGER(PAD_BUTTON_X) )
            {
                cellAPos = CELL_INIT_POS;
                mainSurface.viewRect.posTopLeft = SURFACE_MAIN_INIT_POS;
                subSurface.viewRect.posTopLeft  = SURFACE_SUB_INIT_POS;
            }
            
            // Rotation ON/OFF
            if( G2DDEMO_IS_TRIGGER(PAD_BUTTON_A) )
            {
                bRotate = bRotate^TRUE;
            }
            
            //
            // ON/OFF for OBJ unit visible culling.
            //
            if( G2DDEMO_IS_TRIGGER(PAD_BUTTON_START) )
            {
                if( mainSurface.pBeforeDrawOamBackFunc == NULL )
                {
                    mainSurface.pBeforeDrawOamBackFunc  = OBJCullingFunc_;
                    subSurface.pBeforeDrawOamBackFunc   = OBJCullingFunc_;
                }else{
                    mainSurface.pBeforeDrawOamBackFunc  = NULL;
                    subSurface.pBeforeDrawOamBackFunc   = NULL;
                }
            }
        }
        //------------------------------------------------------
        // Drawing
        //
        // Render cell using Renderer module
        //
        {
            NNS_G2dBeginRendering( &render );
                    
                // Movement possible with cell key operation.
                NNS_G2dPushMtx();
                    NNS_G2dTranslate( cellAPos.x, cellAPos.y, 0 );
                    
                    if( bRotate )
                    {
                        NNS_G2dRotZ( FX_SinIdx( rot ), FX_CosIdx( rot ) );
                    }
                    
                    // Measures time required for drawing.
                    time = OS_GetTick();
                        NNS_G2dDrawCellAnimation( spCellAnim );
                    time = OS_GetTick() - time;
                    
                NNS_G2dPopMtx();
                
            NNS_G2dEndRendering();
        }
        //------------------------------------------------------
        // Output display information
        {
            G2DDemo_PrintOutf( 0, 0, "TimeForDraw  : %06ld usec\n", OS_TicksToMicroSeconds(time) );
            G2DDemo_PrintOutf( 0, 1, "OBJ-Culling  : %s", ( mainSurface.pBeforeDrawOamBackFunc != NULL ) ? "ON ":"OFF");
            G2DDemo_PrintOutf( 0, 2, "Rotate       : %s", ( bRotate ) ? "ON ":"OFF");
        }
        //------------------------------------------------------
        // Wait for V-Blank
        G3_SwapBuffers(GX_SORTMODE_AUTO, GX_BUFFERMODE_Z);
        SVC_WaitVBlankIntr();

        //------------------------------------------------------
        //
        // Write buffer contents to HW
        //
        {
            // Cell
            NNS_G2dApplyAndResetOamManagerBuffer( &sMainOamMan );
            NNS_G2dApplyAndResetOamManagerBuffer( &sSubOamMan );
            
            // Display information
            G2DDemo_PrintApplyToHW();
        }
        
        //------------------------------------------------------
        // scene update
        rot += 0x100;
        NNS_G2dTickCellAnimation( spCellAnim, FX32_ONE );
    }
}

/*---------------------------------------------------------------------------*
  Name:         VBlankIntr

  Description:  Handles V-blank interrupts.

  Arguments:    None.

  Returns:      None.
 *---------------------------------------------------------------------------*/
void VBlankIntr(void)
{
    OS_SetIrqCheckFlag( OS_IE_V_BLANK );                   // checking VBlank interrupt
}



