/*---------------------------------------------------------------------------* Project: SwingDemo File: swing.c Programmer: Keizo Ohta Copyright 2005-2007 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. *---------------------------------------------------------------------------*/ #include #include #include #include #include "kfont.h" #include "graphic.h" /*************************************************************** GXFIFO ***************************************************************/ #define GX_FIFO_SIZE ( 1 * 1024*1024 ) // Large enough (The smaller the FIFO, the larger the chance of waiting for space to open when a command is issued.) static void *gx_fifo_p ; // Pointer to buffer static GXFifoObj *gx_fifo_obj ; // Management structure /*************************************************************** Frame buffer ***************************************************************/ GXRenderModeObj *rmode_p ; // Pointer to the rendering mode static u8 vfilter[7] = { 0,8, 16,16,16, 8,0 } ; static void *xfb_p[ 2 ] ; // Pointer to double buffer static s32 draw_xfb_idx ; // XFB being drawn by GX static s32 disp_xfb_idx ; // XFB being displayed by VI /*************************************************************** Controllers ***************************************************************/ #define KPAD_BUF_SIZE 16 KPADStatus kpads[ WPAD_MAX_CONTROLLERS ][ KPAD_BUF_SIZE ] ; s32 kpad_reads ; /******************************************************************************* Memory initialization *******************************************************************************/ static void init_memory( void ) { void *arenaLo, *arenaHi ; OSHeapHandle heap ; //----- Check available memory arenaLo = OSGetArenaLo() ; arenaHi = OSGetArenaHi() ; //----- Declare the creation of one heap that takes up all available memory arenaLo = OSInitAlloc( arenaLo, arenaHi, 1 ) ; OSSetArenaLo( arenaLo ) ; // Reset because the available memory changes //----- Create a heap which takes up all available memory and set it as the current heap heap = OSCreateHeap( arenaLo, arenaHi ) ; (void)OSSetCurrentHeap( heap ) ; //----- Make it clear ahead of time that available memory has been utilized arenaLo = arenaHi ; OSSetArenaLo( arenaLo ) ; } /******************************************************************************* Initialize memory (MEM2) *******************************************************************************/ static MEMAllocator s_mem2Allocator; static MEMHeapHandle s_handle; static void init_memory2(void) { void *lo = OSGetMEM2ArenaLo(); void *hi = OSGetMEM2ArenaHi(); s_handle = MEMCreateFrmHeap(lo, (u32)hi - (u32)lo); if ( s_handle == MEM_HEAP_INVALID_HANDLE ) { OSHalt("MEM2 heap allocation error.\n"); } else { OSSetMEM2ArenaLo(hi); MEMInitAllocatorForFrmHeap(&s_mem2Allocator, s_handle, 32); // Buffer requires 32byte alignment. } } static void* alloc32(u32 size) { return MEMAllocFromAllocator(&s_mem2Allocator, size); } static u8 free32(void *addr) { MEMFreeToAllocator(&s_mem2Allocator, addr); return 1; } /******************************************************************************* Initialize relationship with display device *******************************************************************************/ static void init_display( void ) { u32 xfb_size ; //----- Select drawing mode rmode_p = &GXNtsc480IntDf ; //----- Allocate frame buffer xfb_size = VIPadFrameBufferWidth(rmode_p->fbWidth) * rmode_p->xfbHeight * (u32)VI_DISPLAY_PIX_SZ ; xfb_p[0] = OSAlloc( xfb_size ) ; xfb_p[1] = OSAlloc( xfb_size ) ; //----- Select a drawing buffer and display buffer draw_xfb_idx = 0 ; disp_xfb_idx = 1 ; //----- Initialize VI VIConfigure( rmode_p ) ; VISetNextFrameBuffer( xfb_p[ disp_xfb_idx ] ) ; VIFlush() ; VIWaitForRetrace() ; // In order to enable Configure, VIWaitForRetrace() ; // you need to wait two times. } /******************************************************************************* Initialize GX relationship *******************************************************************************/ static void init_gx( void ) { GXColor clear_clr = { 0,0,0, 0 } ; //----- Create GXFIFO gx_fifo_p = OSAlloc( GX_FIFO_SIZE ) ; gx_fifo_obj = GXInit( gx_fifo_p, GX_FIFO_SIZE ) ; //----- Select pixel format GXSetPixelFmt( GX_PF_RGB8_Z24, GX_ZC_LINEAR ) ; GXSetDither( GX_DISABLE ) ; //----- Initialize settings for copying from EFB to XFB GXSetDispCopySrc( 0, 0, rmode_p->fbWidth, rmode_p->efbHeight ) ; GXSetDispCopyDst( rmode_p->fbWidth, rmode_p->xfbHeight ) ; (void)GXSetDispCopyYScale( (f32)(rmode_p->xfbHeight) / (f32)(rmode_p->efbHeight) ) ; GXSetDispCopyGamma( GX_GM_1_0 ) ; GXSetCopyFilter( rmode_p->aa, rmode_p->sample_pattern, GX_ENABLE, vfilter ) ; GXSetCopyClear( clear_clr, 0x00FFFFFF ) ; //----- Initialize EFB drawing area GXSetViewport( 0.0f, 0.0f, (f32)(rmode_p->fbWidth), (f32)(rmode_p->efbHeight), 0.0f, 1.0f ) ; GXSetScissor( 0, 0, (u32)rmode_p->fbWidth, (u32)rmode_p->efbHeight ) ; //----- Clear EFB and XFB GXCopyDisp( xfb_p[0], GX_ENABLE ) ; // The EFB is cleared and garbage is placed in XFB[0] GXCopyDisp( xfb_p[0], GX_DISABLE ) ; // The cleared EFB is placed in XFB[0] GXCopyDisp( xfb_p[1], GX_DISABLE ) ; // The cleared EFB is placed in XFB[1] GXDrawDone() ; } /*************************************************************** Adjustment item ***************************************************************/ #define ACC_PLAY_RADIUS 0.0f // Tolerance radius of KPAD acceleration #define ACC_SENSITIVITY 1.0f // Tracking of KPAD acceleration #define ACC_MAX 3.3999f // Maximum acceleration range #define ACC_DAMP 0.6f // Attenuation rate of acceleration changes outside the range #define DIR_VEC_DAMP 0.9f // Attenuation rate of the speed vector (0-1) #define DIR_SPEED_MIN 1.9f // Minimum speed required for recognition #define DIR_SPEED_DAMP 0.98f // Attenuation rate of the speed record (0-1) /*----- Methods of adjustment ACC_XXX is okay as it is. DIR_XXX has room for adjustment depending on the circumstances of the game. Increasing the value of DIR_VEC_DAMP or decreasing the value of DIR_SPEED_MIN will allow even light swings to be recognized, but erroneous recognitions will also increase. In a similar vein, increasing the value of DIR_SPEED_DAMP will lengthen the time taken from one recognition to the next, but may also increase recognition accuracy. -----*/ /*************************************************************** Recognition data ***************************************************************/ Vec dir_nrm ; // Swinging direction/length 1 s32 dir_nrm_pass_count ; // Time elapsed since update; in units of 1/200 sec /*----- How to Use The direction in which the swinging occurred is always updated in the acceleration coordinate system in dir_nrm. In a game, this would involve referencing dir_nrm the instant that dir_nrm_pass_count exceeds a certain value. Decide how much time should pass before using it depending on the circumstances of the game. Referencing it early will result in good response, but might lower the accuracy. This sample changes the color of the arrow from red to yellow and then to blue, depending on the time elapsed. In addition, dir_nrm is a 3D vector variable, but since it has been customized for 2D in this sample, Z is always 0. -----*/ /*************************************************************** Variables for internal processing ***************************************************************/ static Vec last_acc ; // Previous acceleration static Vec acc_vec ; // Amount of change in acceleration static Vec dir_vec ; // The aerial speed that is accelerated by the amount of change in acceleration static f32 dir_speed_max ; // The record for speed /******************************************************************************* Initialization Functions *******************************************************************************/ static void init_sample( void ) { //----- Acceleration settings for KPAD KPADSetAccParam ( 0, ACC_PLAY_RADIUS, ACC_SENSITIVITY ) ; //----- Initialize the swing direction appropriately dir_nrm.x = 1.0f ; dir_nrm.y = 0.0f ; dir_nrm.z = 0.0f ; dir_nrm_pass_count = 0 ; //----- Initialize variables for other processing last_acc.x = last_acc.y = last_acc.z = acc_vec.x = acc_vec.y = acc_vec.z = dir_vec.x = dir_vec.y = dir_vec.z = dir_speed_max = 0.0f ; } /******************************************************************************* CPU process function *******************************************************************************/ static void work_sample( void ) { Vec new_acc ; f32 f1 ; s32 ct ; //----- Main recognition program (looks at the overall acceleration sampled at 200 Hz) ct = kpad_reads ; while ( --ct >= 0 ) { //----- Measurement of the elapsed time for swing direction recognition ++ dir_nrm_pass_count ; //----- Get the most recent acceleration new_acc = kpads[0][ ct ].acc ; // If getting the acceleration results in an error, it is probably best to perform the same predictive processing that is used when the acceleration measurement limit is exceeded, as shown below. // //----- Get the amount of change in acceleration from the previous sample, with predicting calculation if ( new_acc.x < -ACC_MAX || new_acc.x > ACC_MAX ) { new_acc.x = ( acc_vec.x *= ACC_DAMP ) + last_acc.x ; } else { acc_vec.x = new_acc.x - last_acc.x ; } if ( new_acc.y < -ACC_MAX || new_acc.y > ACC_MAX ) { new_acc.y = ( acc_vec.y *= ACC_DAMP ) + last_acc.y ; } else { acc_vec.y = new_acc.y - last_acc.y ; } if ( new_acc.z < -ACC_MAX || new_acc.z > ACC_MAX ) { new_acc.z = ( acc_vec.z *= ACC_DAMP ) + last_acc.z ; } else { acc_vec.z = new_acc.z - last_acc.z ; } //----- Save the last acceleration last_acc = new_acc ; //----- Attenuate the speed that is accelerated by the amount of change dir_vec.x *= DIR_VEC_DAMP ; dir_vec.y *= DIR_VEC_DAMP ; dir_vec.z *= DIR_VEC_DAMP ; //----- The further the Z change amount is from zero, the more to increase the X speed and Y speed; always set the Z speed to zero. f1 = acc_vec.z ; if ( f1 < 0.0f ) f1 = -f1 ; f1 = 1.0f - f1 * f1 ; if ( f1 > 0.0f ) { dir_vec.x += f1 * acc_vec.x ; dir_vec.y += f1 * acc_vec.y ; } dir_vec.z = 0.0f ; // If acceleration is done straightforwardly for all three axes, it is possible to recognize in three dimensions. //---- Dampen the speed record dir_speed_max *= DIR_SPEED_DAMP ; //----- Update check for the speed record; a minimum amount of speed is required f1 = sqrtf( dir_vec.x * dir_vec.x + dir_vec.y * dir_vec.y + dir_vec.z * dir_vec.z ) ; if ( f1 >= DIR_SPEED_MIN && f1 > dir_speed_max ) { //----- Update the swing direction!!! dir_speed_max = f1 ; f1 = 1.0f / f1 ; dir_nrm.x = f1 * dir_vec.x ; dir_nrm.y = f1 * dir_vec.y ; dir_nrm.z = f1 * dir_vec.z ; dir_nrm_pass_count = 0 ; } } } /******************************************************************************* Drawing function *******************************************************************************/ static void draw_sample( void ) { f32 r = 200.0f ; // Length of the arrow GXColor clr ; if ( dir_nrm_pass_count < 10 ) clr = red_clr ; // The color just after recognition else if ( dir_nrm_pass_count < 20 ) clr = yellow_clr ; // The color after some time has passed else clr = blue_clr ; // The color after even more time has passed init_draw_graphic( rmode_p->fbWidth, rmode_p->efbHeight ) ; draw_arrow( r*dir_nrm.x, r*dir_nrm.y, -r*dir_nrm.x, -r*dir_nrm.y, clr, 10 ) ; // The acceleration coordinate system of KPAD is in the opposite direction as the rendering coordinate system. } /******************************************************************************* Main *******************************************************************************/ void main( void ) { /*********************************************************************** Initialization processing ***********************************************************************/ //----- Highest-priority hardware initialization VIInit() ; //----- Other priority initializations init_memory() ; // Enable allocation and release of memory init_memory2(); init_display() ; // Display device-related init_gx() ; // GX-related WPADRegisterAllocator(alloc32, free32); KPADInitEx( NULL, 0 ) ; // Controllers //----- Initialize application init_kfont_texture() ; init_sample() ; //----- Screen display ON VISetBlack( FALSE ) ; VIFlush() ; /*********************************************************************** Main loop ***********************************************************************/ while (1) { /*************************************************************** First, perform calculations by the CPU Find coordinates based on controller input, and prepare the data necessary to issue a GX command ***************************************************************/ //----- Load controller kpad_reads = KPADReadEx( 0, &kpads[0][0], KPAD_BUF_SIZE, NULL ) ; //----- Various calculations work_sample() ; /*************************************************************** Issue GX command and go on drawing to EFB ***************************************************************/ //----- Cast spell to start issuing the command GXInvalidateVtxCache() ; GXInvalidateTexAll() ; //----- Issue various commands draw_sample() ; //----- Finally, copy from EFB to XFB GXCopyDisp( xfb_p[ draw_xfb_idx ], GX_ENABLE ) ; GXDrawDone() ; /*************************************************************** The XFB copied here can be selected for display on the TV ***************************************************************/ disp_xfb_idx = draw_xfb_idx ; VISetNextFrameBuffer( xfb_p[ disp_xfb_idx ] ) ; VIFlush() ; //----- Wait for the display to actually switch VIWaitForRetrace() ; //----- Switch to the no longer displayed XFB ahead of time for the next drawing draw_xfb_idx ^= 1 ; } }