/*
 *	main.c
 *
 *	3D Benchもどき
 *
 *	* Thu Feb 07 23:15:58 JST 2008 Naoyuki Sawa
 *	- 1st リリース。
 *	* Sun Apr 06 22:46:42 JST 2008 Naoyuki Sawa
 *	- 高速化により30FPS以上出るようになったので、30FPSのリミッタを外しました。
 */
#include "app.h"

/****************************************************************************
 *	
 ****************************************************************************/

#ifdef USE_MUSIC
#include "instdef2.c"
#endif /*USE_MUSIC*/

#ifdef USE_SOUND
const void* sound_table[SOUND_COUNT];
#endif /*USE_SOUND*/

#ifdef USE_TEXTURE
TEXTURE texture_table[TEXTURE_COUNT];
#endif /*USE_TEXTURE*/

#ifdef USE_SPRITE
#define SPRITE_ SPRITE_INIT2
const SPRITE sprite_table[SPRITE_COUNT] = {
#include "sprite.h"
};
#undef SPRITE_
#endif /*USE_SPRITE*/

#define extern
#include "work.h"
#undef extern

/****************************************************************************
 *	main
 ****************************************************************************/

int
main()
{
	/* 初期化。 */
	app_init();

	/* メインループ。 */
	app_main();

	return 0;
}

/****************************************************************************
 *	app_init
 ****************************************************************************/

void
app_init()
{
	/* 一般的な初期化。 */
	pceAppSetProcPeriod(1000 / FPS);
#ifdef V_SCREEN
	pceLCDSetBuffer(_def_vbuff);
#else /*V_SCREEN*/
	pceLCDSetBuffer(vbuff);
#endif /*V_SCREEN*/
	pceLCDDispStart();
#ifdef REPEAT_PAD
	pad_set_repeat(REPEAT_PAD, REPEAT_DELAY, REPEAT_INTERVAL);
#endif /*REPEAT_PAD*/

	/* 描画環境初期化。 */
	surface.w = DISP_X;
	surface.h = DISP_Y;
	surface.vbuff = vbuff;
#ifdef USE_3D
	surface.zbuff = zbuff;
	render_init_3d(&render, &surface, 0, 0, 0, 0, FRONT, BACK, FOVY);
#else /*USE_3D*/
	render_init_2d(&render, &surface, 0, 0, 0, 0);
#endif /*USE_3D*/

#if defined(TRACE_ON) && (TRACE_ON)
	/* 標準エラー出力を開きます。 */
	stderr_open(9/*標準エラー出力モニタ接続待ち時間(調整可)*/);
#endif /*TRACE_ON*/

#ifdef USE_UFE
	/* USBファイルシステムエミュレーションを開始します。 */
	ufe_setup(9/*PC側ファイルサーバ接続待ち時間(調整可)*/);
#endif /*USE_UFE*/

#ifdef RESOURCE_FNAME
	/* pexファイル添付リソースを取得します。 */
	resource = resource_address(RESOURCE_FNAME);
#endif /*RESOURCE_FNAME*/

#ifdef TURBO
	/* クロック・メモリウェイトを切替えます。 */
	turbo(TURBO);
#endif /*TURBO*/

#ifdef USE_DBG
	/* DBGユニットを設定します。 */
	dbg_init();
#endif /*USE_DBG*/

#ifdef USE_GC
	/* ガーベージコレクターを初期化します。 */
	gc_init();
#endif /*USE_GC*/

#ifdef USE_TCPIP
	/* TCP/IPプロトコルスイートを開始します。 */
	tcpip_start(0/*Ethernetドライバ送信バッファサイズ(調整可)*/);
#endif /*USE_TCPIP*/

#ifdef LDIRECT_INTERVAL
	/* 高速切替・16階調表示を初期化します。 */
	ldirect_init(LDIRECT_INTERVAL);
#endif /*LDIRECT_INTERVAL*/

#ifdef USE_MUSIC
	/* 音楽ライブラリを初期化します。 */
	music_loudness(1);
	loadInst(); /* 「Randomism Zone(http://www8.plala.or.jp/randomism/)」さんの『ドラム音色分離キットver.2.10』を使用 */
	InitMusic();
#endif /*USE_MUSIC*/

	/* クリーンアップ関数を登録。 */
	atexit(app_exit);

#ifdef USE_2BIT_ADPCM
	/* 2-bit ADPCM Decoderを起動します。 */
	adpcm_init(ADPCM_CHANNELS);
#endif /*USE_2BIT_ADPCM*/

	/* サウンドを初期化します。 */
#ifdef USE_SOUND
	{
#define SOUND_(name) { extern const unsigned char name[]; sound_table[SND_##name] = name; }
#define SOUND_RESOURCE_(name,fname) { sound_table[SND_##name] = fpk_address(resource, fname, NULL); }
#include "sound.h"
#undef SOUND_
#undef SOUND_RESOURCE_
	}
#endif /*USE_SOUND*/

	/* テクスチャを初期化します。 */
#ifdef USE_TEXTURE
	{
#define TEXTURE_(name) extern const unsigned char name[];
#define TEXTURE_RESOURCE_(name,fname) const void* const name = fpk_address(resource, fname, NULL);
#include "texture.h"
#undef TEXTURE_
#undef TEXTURE_RESOURCE_
		texture_table_init(texture_table, TEXTURE_COUNT,
#define TEXTURE_(name) name,
#define TEXTURE_RESOURCE_(name,fname) name,
#include "texture.h"
#undef TEXTURE_
#undef TEXTURE_RESOURCE_
		NULL/*最後の','を吸収するためのダミー。texture_table_init()に対しては無意味です。*/);
	}
#endif /*USE_TEXTURE*/

#ifdef USE_SPRITE
	/* 簡易スプライトドライバを初期化します。 */
	sprite_init(&render, sprite_table, SPRITE_COUNT, texture_table, TEXTURE_COUNT);
#endif /*USE_SPRITE*/

#ifdef USE_VIDREC
	/* 画面出力を初期化します。 */
	yield(); /* runで実行した場合の実行確認に応答するため、USB機能の切り替えを僅かに遅らせる */
#ifdef LDIRECT_INTERVAL
	cdcacm_start(0, sizeof vbuff / 2, NULL, NULL);
#else /*LDIRECT_INTERVAL*/
	cdcacm_start(0, sizeof vbuff / 4, NULL, NULL);
#endif /*LDIRECT_INTERVAL*/
#endif /*USE_VIDREC*/

/*{{アプリケーション特有の初期化*/
	srand(pceTimerGetCount());
/*}}アプリケーション特有の初期化*/
}

/****************************************************************************
 *	app_exit
 ****************************************************************************/

void
app_exit()
{
/*{{アプリケーション特有のクリーンアップ*/
	/** no job **/
/*}}アプリケーション特有のクリーンアップ*/

#ifdef USE_VIDREC
	/* 画面出力を終了します。 */
	cdcacm_stop();
#endif /*USE_VIDREC*/

#ifdef USE_MUSIC
	/* 確実に音楽演奏を停止します。
	 * サウンドの停止はライブラリ内のpceAppExit()で行っているので、
	 * ここでは必要ありません。
	 */
	StopMusic();
	music_loudness(0);
#endif /*USE_MUSIC*/

#ifdef LDIRECT_INTERVAL
	/* 高速切替・16階調表示を解除します。 */
	ldirect_free();
#endif /*LDIRECT_INTERVAL*/

#ifdef USE_TCPIP
	/* TCP/IPプロトコルスイートを終了します。 */
	tcpip_stop();
#endif /*USE_TCPIP*/

#ifdef USE_GC
	/* ガーベージコレクターを解除します。 */
	gc_free();
#endif /*USE_GC*/

#ifdef USE_DBG
	/* DBGユニットの設定を解除します。 */
	dbg_free();
#endif /*USE_DBG*/

#ifdef USE_UFE
	/* USBファイルシステムエミュレーションを終了します。 */
	ufe_stop();
#endif /*USE_UFE*/
}

/****************************************************************************
 *	update
 ****************************************************************************/

/* * Mon Nov 06 17:37:17 JST 2006 Naoyuki Sawa
 * - schedule()から、画面更新処理だけを分離しました。
 *   フレーム待ち無し画面更新だけを行う場合は、この関数を呼び出してください。
 */
void
update()
{
#ifdef V_SCREEN
	/* vbuff(縦画面)→_def_vbuff(横画面)変換 */
	unsigned char *dst = &_def_vbuff[0];     /* 転送先の横画面は左上から走査開始します */
	unsigned char *src = &vbuff[DISP_X - 1]; /* 転送元の縦画面は右上から走査開始します */
	int x, y;
	for(x = DISP_X - 1; x >= 0; x--) {
		for(y = 0; y < DISP_Y; y++) {
			*dst = *src;
			dst += 1;      /* 横画面を右方向に走査します */
			src += DISP_X; /* 縦画面を下方向に走査します */
		}
		                            /* 横画面は既に次の行へ移動しています */
		src -= DISP_X * DISP_Y + 1; /* 縦画面を左の列の一番上へ移動します */
	}
#endif /*V_SCREEN*/

	/* 画面転送 */
	pceLCDTrans();
}

/****************************************************************************
 *	vidrec
 ****************************************************************************/

#ifdef USE_VIDREC
static void
vidrec()
{
#ifdef LDIRECT_INTERVAL
	static const unsigned char hdr[4] = { DISP_X,DISP_Y,4/*[bpp]*/,FPS };
#else /*LDIRECT_INTERVAL*/
	static const unsigned char hdr[4] = { DISP_X,DISP_Y,2/*[bpp]*/,FPS };
#endif /*LDIRECT_INTERVAL*/
	static int active;
	//
	unsigned char buf[128/*調整可*/]; /* (sizeof vbuff)/Nのサイズとすること */
	const unsigned char* src;
	unsigned char* dst;

	if(cdcacm_recv(buf, 1) > 0) {
		switch(buf[0]) {
		case 0:
			active = 0; /* 画面出力停止 */
			break;
		case 1:
			active = 1; /* 画面出力開始 */
			src = hdr;
			do {
				if(pcePadGetDirect() & PAD_SELECT) {
					exit(0); /* 送信待ちに割り込んで強制終了 */
				}
				src += cdcacm_send(src, &hdr[sizeof hdr] - src);
			} while(src < &hdr[sizeof hdr]);
			break;
		}
	}
	if(active) {
		src = vbuff;
		do {
			dst = buf;
			do {
#ifdef LDIRECT_INTERVAL
				*dst++ = src[0]|(src[1]<<4); 
				src += 2;
#else /*LDIRECT_INTERVAL*/
				*dst++ = src[0]|(src[1]<<2)|(src[2]<<4)|(src[3]<<6);
				src += 4;
#endif /*LDIRECT_INTERVAL*/
			} while(dst < &buf[sizeof buf]);
			dst = buf;
			do {
				if(pcePadGetDirect() & PAD_SELECT) {
					exit(0); /* 送信待ちに割り込んで強制終了 */
				}
				dst += cdcacm_send(dst, &buf[sizeof buf] - dst);
			} while(dst < &buf[sizeof buf]);
		} while(src < &vbuff[sizeof vbuff]);
	}
}
#endif /*USE_VIDREC*/

/****************************************************************************
 *	schedule
 ****************************************************************************/

/* * Fri Dec 15 00:49:15 JST 2006 Naoyuki Sawa
 * - schedule()関数に、サウンドポーズ対応コードを追加しました。
 *   少し試した限りでは上手く動作しているようですが、スリープからの復帰時に問題が起きないか未確認です。
 * - もし、ポーズ状態のまま、すなわち、サウンドポーズ状態のままでアプリケーションを終了してしまった場合にも、
 *   CLiPライブラリ内のpceAppExit()でpceWaveStop(1)を呼び出しており、サウンド状態がリセットされるので、大丈夫です。
 */

void
schedule()
{
#ifdef USE_PAUSE
	{
		/* PAUSEメッセージ領域のイメージ退避用バッファは、324バイト必要です。
		 * スタック上に取るのは危険ですし、staticに取るのもメモリの無駄です。
		 * そこで、PAUSE時にのみヒープから確保することにします。
		 */
		//unsigned char save[PAUSE_H][PAUSE_W];
		unsigned char (*save)[PAUSE_W] = NULL/*警告抑制*/;
		int pause = 0;
		/*{{2006/12/15:サウンドポーズ対応*/
		int sound_pause = 0/*警告抑制*/;
		/*}}2006/12/15:サウンドポーズ対応*/
		int x, y;
		for(;;) {
#endif /*USE_PAUSE*/
			/* 画面更新。 */
			update();
#ifdef USE_VIDREC
			/* 画面出力。 */
			vidrec();
#endif /*USE_VIDREC*/
			/* システム処理。 */
			yield();
			/* パッド状態更新。 */
			joy = pad_get();
#ifdef V_SCREEN
			joy = (joy & ~(PAD_LF | PAD_RI | PAD_UP | PAD_DN | /* 縦画面用入力変換 */
				       TRG_LF | TRG_RI | TRG_UP | TRG_DN))
				| (joy & PAD_LF ? PAD_UP : 0)
				| (joy & PAD_RI ? PAD_DN : 0)
				| (joy & PAD_UP ? PAD_RI : 0)
				| (joy & PAD_DN ? PAD_LF : 0)
				| (joy & TRG_LF ? TRG_UP : 0)
				| (joy & TRG_RI ? TRG_DN : 0)
				| (joy & TRG_UP ? TRG_RI : 0)
				| (joy & TRG_DN ? TRG_LF : 0);
#endif /*V_SCREEN*/

#ifdef USE_PAUSE
			/* PAUSE処理。 */
			if(joy & TRG_START) {
				pause = !pause;	/* PAUSE状態切り替え */
				if(pause) {	/* PAUSE IN */
					/*{{2006/12/15:サウンドポーズ対応*/
					ENTER_CS;
					sound_pause = fINT_EDMA.EHDM1;
					if(sound_pause) {
						fINT_EDMA.EHDM1 = 0;
					}
					LEAVE_CS;
					/*}}2006/12/15:サウンドポーズ対応*/
					/* PAUSEメッセージ表示部分のイメージを退避。 */
					save = (unsigned char (*)[PAUSE_W])malloc(PAUSE_W * PAUSE_H); /* イメージ退避用バッファ確保 */
					if(save == NULL) DIE();
					for(y = 0; y < PAUSE_H; y++) {
						for(x = 0; x < PAUSE_W; x++) {
							save[y][x] = vbuff[DISP_X * (PAUSE_Y + y) + (PAUSE_X + x)];
						}
					}
				} else {	/* PAUSE OUT */
					/*{{2006/12/15:サウンドポーズ対応*/
					ENTER_CS;
					if(sound_pause) {
						fINT_EDMA.EHDM1 = 1;
					}
					LEAVE_CS;
					/*}}2006/12/15:サウンドポーズ対応*/
					/* PAUSEメッセージ表示部分のイメージを復帰。 */
					for(y = 0; y < PAUSE_H; y++) {
						for(x = 0; x < PAUSE_W; x++) {
							vbuff[DISP_X * (PAUSE_Y + y) + (PAUSE_X + x)] = save[y][x];
						}
					}
					free(save); /* イメージ退避用バッファ開放 */
				}
			}
			if(!pause) break;

			/* PAUSEメッセージ点滅表示。 */
			if(pceTimerGetCount() % 1000 < 750) {
				render_string_framed(&render, PAUSE_X, PAUSE_Y, PAUSE_STR, PAUSE_FONT, PAUSE_COLOR);
			} else {
				for(y = 0; y < PAUSE_H; y++) {
					for(x = 0; x < PAUSE_W; x++) {
						vbuff[DISP_X * (PAUSE_Y + y) + (PAUSE_X + x)] = save[y][x];
					}
				}
			}
		}
	}
#endif /*USE_PAUSE*/

	if(joy & TRG_SELECT) {
		exit(0);
	}

	now++;	/* グローバルタイマ更新 */
}

/****************************************************************************
 *	delay
 ****************************************************************************/

void
delay(int frame)
{
	while(frame > 0) {
		schedule();
		frame--;
	}
}

/****************************************************************************
 *	app_main
 ****************************************************************************/

extern const MESH MESH_FLOOR;
extern const MESH MESH_COMPUTER;
extern const MESH MESH_KEYBOARD;
extern const MESH MESH_KEY_X;
extern const MESH MESH_KEY_Y;
extern const MESH MESH_KEY_Z;
extern const MESH MESH_KEY_H;
extern const MESH MESH_KEY_R;
extern const MESH MESH_KEY_D;
extern const MESH MESH_BOARD;
extern const MESH MESH_FONT_H;
extern const MESH MESH_FONT_A;
extern const MESH MESH_FONT_L;

unsigned char bgbuff[DISP_X * DISP_Y];

const MESH* const key_mesh[6] = {
	&MESH_KEY_X,&MESH_KEY_Y,&MESH_KEY_Z,
	&MESH_KEY_H,&MESH_KEY_R,&MESH_KEY_D,
};
const fixed key_pos[6][2] = {
	{fld(-0.6),fld( 0.3)},{fld( 0.0),fld( 0.3)},{fld(0.6),fld( 0.3)},
	{fld(-0.6),fld(-0.3)},{fld( 0.0),fld(-0.3)},{fld(0.6),fld(-0.3)},
};

const MESH* const font_mesh[3] = {
	&MESH_FONT_H,&MESH_FONT_A,&MESH_FONT_L,
};
fixed font_pos[3][3/*x,y,vy*/] = {
	{ fld(1.75-0.75), fld(0.0), fld(0.0) },
	{ fld(1.75     ), fld(0.0), fld(0.0) },
	{ fld(1.75+0.75), fld(0.0), fld(0.0) },
};

#define Trend		(render.context->matrix)

#define VIEW_Y		10
#define VIEW_H		68
#undef FRONT
#undef BACK
#undef FOVY
#define FRONT		fld( 0.5)
#define BACK		fld(25.0)
#define FOVY		DEG(45.0)

int loop;
int step;

#define computer_x	fld(-1.5)
#define computer_y	fld( 2.0)
#define computer_z	fld( 0.25)
fixed computer_rotx;
fixed computer_roty;
fixed computer_rotz;

#define keyboard_x	fld( 1.5)
#define keyboard_y	fld( 0.0)
#define keyboard_z	fld(-2.5)
int key_sel;
int key_cnt;

#define board_x		fld(1.75)
#define board_y		fld(2.25)
#define board_z		fld(1.5)

fixed font_a_roty;
fixed font_l_rotz;

static void
bg_init()
{
	int x;
	int y;

	render_init_2d(&render, &surface, 0, 0, 0, 0);

	render.context->texture = &texture_table[TEX_TTL];
	render_object(&render, 0, 0, 0, 0, DISP_X, DISP_Y, DRW_NOMAL);
	for(x = 0; x < FSEC(4,10); x++) {
		schedule();
		if(x >= SEC(0.5)) {
			if(joy & TRG_AB) {
				break;
			}
		}
	}
	surface_clear(&surface, 3);
	delay(SEC(0.3));

//{{2008/04/06:追加
// * Sun Apr 06 22:46:42 JST 2008 Naoyuki Sawa
// - 高速化により30FPS以上出るようになったので、30FPSのリミッタを外します。
//   最初から外してしまうと、タイトル画面が一瞬で終わってしまうので、
//   そうならないよう、タイトル画面表示完了後にリミッタを外すことにします。
	pceAppSetProcPeriod(1);
//}}2008/04/06:追加

	for(y = 0; y < DISP_Y; y++) {
		for(x = 0; x < DISP_X; x++) {
			render_point(&render, x, y, (x ^ y) & 1 ? 2 : 3);
		}
	}
	render.context->texture = &texture_table[TEX_PAT];
	render_object(&render, 0,               0, 0, 224, DISP_X, VIEW_Y, DRW_NOMAL);
	render_object(&render, 0, DISP_Y - VIEW_Y, 0, 240, DISP_X, VIEW_Y, DRW_NOMAL);
	memcpy(bgbuff, vbuff, DISP_X * DISP_Y);

	render_init_3d(&render, &surface, 0, VIEW_Y, DISP_X, VIEW_H, FRONT, BACK, FOVY);
}

typedef struct _LOOKAT {
	int step;
	fixed v[6];
} LOOKAT;

const LOOKAT lookat_tbl[/*MAX_LOOKAT*/] = {
	{ FSEC( 6,25)-FSEC(4,10), { fld( 0.00),fld( 2.00),fld(-6.00), fld( 0.00),fld( 0.00),fld( 3.00) } },	/* 初期(停止) */
	{ FSEC( 7,25)-FSEC(4,10), { fld( 0.00),fld( 1.00),fld(-5.50), fld( 0.00),fld( 0.90),fld(-0.50) } },	/* 降下 */
	{ FSEC( 8,10)-FSEC(4,10), { fld( 0.00),fld( 0.50),fld(-3.00), fld( 0.00),fld( 0.40),fld( 3.00) } },	/* 前進 */
	{ FSEC( 9,05)-FSEC(4,10), { fld( 1.00),fld( 0.50),fld(-0.50), fld( 1.75),fld( 0.50),fld( 1.50) } },	/* HAL注視 */
	{ FSEC( 9,15)-FSEC(4,10), { fld( 1.00),fld( 0.50),fld(-0.50), fld( 1.75),fld( 0.50),fld( 1.50) } },	/* HAL注視(停止) */
	{ FSEC(10,00)-FSEC(4,10), { fld( 1.00),fld( 0.50),fld(-0.50), fld( 1.75),fld( 2.75),fld( 1.50) } },	/* 見上げ */
	{ FSEC(10,15)-FSEC(4,10), { fld( 1.00),fld( 0.50),fld(-0.50), fld( 1.75),fld( 2.75),fld( 1.50) } },	/* 見上げ(停止) */
	{ FSEC(11,10)-FSEC(4,10), { fld( 1.00),fld( 0.50),fld(-0.50), fld( 1.75),fld( 0.50),fld( 1.50) } },	/* HAL注視 */
	{ FSEC(12,20)-FSEC(4,10), { fld( 1.00),fld( 0.50),fld(-0.50), fld( 1.75),fld( 0.50),fld( 1.50) } },	/* HAL注視(停止) */
	{ FSEC(13,05)-FSEC(4,10), { fld( 1.00),fld( 0.50),fld(-0.50), fld( 1.75),fld( 2.75),fld( 1.50) } },	/* 見上げ */
	{ FSEC(13,15)-FSEC(4,10), { fld( 1.00),fld( 0.50),fld(-0.50), fld(-1.50),fld( 2.00),fld( 0.25) } },	/* コンピュータ注視 */
	{ FSEC(13,25)-FSEC(4,10), { fld( 1.00),fld( 0.50),fld(-0.50), fld( 1.50),fld( 0.50),fld(-2.50) } },	/* キーボード注視 */
	{ FSEC(15,00)-FSEC(4,10), { fld(-1.00),fld( 7.50),fld( 4.00), fld( 0.00),fld( 0.00),fld( 0.50) } },	/* 回転 */
};
#define MAX_LOOKAT	ARRAY_SIZE(lookat_tbl)

void
get_lookat(LOOKAT* lookat)
{
	int i;
	int j;
	int a;
	int b;

	if(lookat->step < lookat_tbl[0].step) {
		*lookat = lookat_tbl[0];
		return;
	}

	for(i = 0; i < MAX_LOOKAT; i++) {
		if(lookat->step < lookat_tbl[i].step) {
			break;
		}
	}

	if(i == 0) {
		*lookat = lookat_tbl[0];
		return;
	}
	if(i == MAX_LOOKAT) {
		*lookat = lookat_tbl[MAX_LOOKAT - 1];
		return;
	}

	a = lookat_tbl[i].step - lookat_tbl[i - 1].step;
	b =       lookat->step - lookat_tbl[i - 1].step;
	for(j = 0; j < 6; j++) {
		lookat->v[j] = fadd(lookat_tbl[i - 1].v[j],
		                    fidiv(fimul(fsub(lookat_tbl[i    ].v[j],
		                                     lookat_tbl[i - 1].v[j]),
		                                b),
		                          a));
	}
}

typedef struct _POS {
	int step;
	fixed v[3];
} POS;

const POS board_tbl[/*MAX_BOARD*/] = {
	{ FSEC( 5,25)-FSEC(4,10), {    board_x,   board_y,board_z } },	/* 初期(停止) */
	{ FSEC( 7,00)-FSEC(4,10), { fld( 7.00),   board_y,board_z } },	/* 引き抜き */
	{ FSEC( 9,14)-FSEC(4,10), { fld( 7.00),   board_y,board_z } },
	{ FSEC( 9,15)-FSEC(4,10), {    board_x,fld( 0.00),board_z } },	/* 見上げ開始 */
	{ FSEC(10,00)-FSEC(4,10), {    board_x,   board_y,board_z } },	/* 見上げ */
	{ FSEC(10,10)-FSEC(4,10), {    board_x,   board_y,board_z } },
	{ FSEC(11,15)-FSEC(4,10), { fld( 7.00),   board_y,board_z } },	/* 引き抜き */
	{ FSEC(12,19)-FSEC(4,10), { fld( 7.00),   board_y,board_z } },
	{ FSEC(12,20)-FSEC(4,10), {    board_x,fld( 0.00),board_z } },	/* 見上げ開始 */
	{ FSEC(13,05)-FSEC(4,10), {    board_x,   board_y,board_z } },	/* 見上げ */
	{ FSEC(13,15)-FSEC(4,10), {    board_x,   board_y,board_z } },
	{ FSEC(14,20)-FSEC(4,10), { fld( 7.00),   board_y,board_z } },	/* 引き抜き */
};
#define MAX_BOARD	ARRAY_SIZE(board_tbl)

void
get_board(POS* pos)
{
	int i;
	int j;
	int a;
	int b;

	if(pos->step < board_tbl[0].step) {
		*pos = board_tbl[0];
		return;
	}

	for(i = 0; i < MAX_BOARD; i++) {
		if(pos->step < board_tbl[i].step) {
			break;
		}
	}

	if(i == 0) {
		*pos = board_tbl[0];
		return;
	}
	if(i == MAX_BOARD) {
		*pos = board_tbl[MAX_BOARD - 1];
		return;
	}

	a = board_tbl[i].step - board_tbl[i - 1].step;
	b =       pos->step - board_tbl[i - 1].step;
	for(j = 0; j < 3; j++) {
		pos->v[j] = fadd(board_tbl[i - 1].v[j],
		                 fidiv(fimul(fsub(board_tbl[i    ].v[j],
		                                  board_tbl[i - 1].v[j]),
		                             b),
		                       a));
	}
}

void
app_main()
{
	int frame = 0;
	int T0 = 0;
	int dT = 0;
	//
	int i;
	int j;
	matrix Tproj;
	matrix Tview;
	matrix Ttmp1;
	LOOKAT lookat;
	POS board;

	bg_init();

	Tproj = render.context->matrix;

	T0 = pceTimerGetCount();
	for(;;) {
		schedule();
		memcpy(vbuff, bgbuff, DISP_X * DISP_Y);
		memset(zbuff, -1, DISP_X * DISP_Y);

		lookat.step = step;
		get_lookat(&lookat);
		Tview = mlookat(Tproj,
			lookat.v[0], lookat.v[1], lookat.v[2],
			lookat.v[3], lookat.v[4], lookat.v[5],
			fld(0.0));

		render.context->texture = NULL;
		Trend = Tview;
		render_mesh(&render, &MESH_FLOOR);
		memset(zbuff, -1, DISP_X * DISP_Y);

		/* コンピュータ */
		render.context->texture = &texture_table[TEX_PAT];
		Ttmp1 = mxlate(Tview, computer_x, computer_y, computer_z);
		Ttmp1 = mrotzxy(Ttmp1, computer_rotz, computer_rotx, computer_roty);
		Trend = Ttmp1;
		render_mesh(&render, &MESH_COMPUTER);

		/* コンピュータの影 */
		render.context->texture = NULL;
		Ttmp1 = mxlate(Tview, computer_x, fld(0.0), computer_z);
		Ttmp1 = mscale(Ttmp1, fld(1.0), fld(0.0), fld(1.0));
		Ttmp1 = mrotzxy(Ttmp1, computer_rotz, computer_rotx, computer_roty);
		Trend = Ttmp1;
		render_mesh(&render, &MESH_COMPUTER);

		/* コンピュータ回転 */
		if(loop || (step >= SEC(0.5))) {
			fadd_(computer_roty, fld(M_PI2 * 0.009));
			fprem_(computer_roty, PI2);
		}
		if(loop || (step >= SEC(1.0))) {
			fsub_(computer_rotx, fld(M_PI2 * 0.006));
			fprem_(computer_rotx, PI2);
		}
		if(loop || (step >= SEC(1.5))) {
			fadd_(computer_rotz, fld(M_PI2 * 0.004));
			fprem_(computer_rotz, PI2);
		}

		/* キーボード */
		render.context->texture = NULL;
		Ttmp1 = mxlate(Tview, keyboard_x, keyboard_y, keyboard_z);
		Trend = Ttmp1;
		render_mesh(&render, &MESH_KEYBOARD);

		/* キー */
		render.context->texture = &texture_table[TEX_PAT];
		Ttmp1 = mxlate(Ttmp1, fld(0.0), fld(0.42), fld(-0.03));
		Ttmp1 = mrotx(Ttmp1, fld(-M_PI2 / 24));
		for(i = 0; i < 6; i++) {
			if((key_sel == i) && (key_cnt >= SEC(0.1))) {
				Trend = mxlate(Ttmp1, key_pos[i][0], fld(-0.1), key_pos[i][1]);
			} else {
				Trend = mxlate(Ttmp1, key_pos[i][0], fld( 0.0), key_pos[i][1]);
			}
			render_mesh(&render, key_mesh[i]);
		}

		/* キータイピング */
		key_cnt++;
		if(key_cnt >= SEC(0.2)) {
			key_cnt = 0;
			key_sel = RND32_RANGE(seed, 0, 6);
		}

		/* ポード */
		board.step = step;
		get_board(&board);
		render.context->texture = NULL;
		if(fcom(board.v[1], <, fld(7.0))) {
			Ttmp1 = mxlate(Tview, board.v[0], board.v[1], board.v[2]);
			Trend = Ttmp1;
			render_mesh(&render, &MESH_BOARD);
		}
		if(fcom(board.v[1], ==, board_y)) {
			Ttmp1 = mxlate(Tview, board_x, fld(0.0), board_z);
			Trend = Ttmp1;
			render_mesh(&render, &MESH_BOARD);
		}

		/* HAL */
		for(i = 0; i < 3; i++) {
			if(fcom(font_pos[i][0], >=, fsub(board.v[0], fld(1.0)))) {
				font_pos[i][1] = board.v[1];
				font_pos[i][2] = fld(0.0);
				if(i == 2) { /* L */
					font_l_rotz = fld(0.0);
				}
			} else {
				fsub_(font_pos[i][2], fld(0.03));
				fadd_(font_pos[i][1], font_pos[i][2]);
				if(i == 2) { /* L */
					fsub_(font_l_rotz, fld(M_PI2 * 0.0125));
				}
				if(fcom(font_pos[i][1], <, fld(0.0))) {
					fchs_(font_pos[i][1]);
					fchs_(font_pos[i][2]);
					fmul_(font_pos[i][2], fld(0.6));
					if(fcom(font_pos[i][2], <, fld(0.06))) {
						font_pos[i][2] = fld(0.0);
					}
					if(i == 2) { /* L */
						font_l_rotz = fld(0.0);
					}
				}
			}
			Ttmp1 = mxlate(Tview, font_pos[i][0], fadd(font_pos[i][1], fld(0.375)), board_z);
			switch(i) {
			case 1: /* A */
				if(loop || (step >= (FSEC(5,20)-FSEC(4,10)))) {
					fadd_(font_a_roty, fld(M_PI2 * 0.025));
					fprem_(font_a_roty, PI2);
				}
				Ttmp1 = mroty(Ttmp1, font_a_roty);
				break;
			case 2: /* L */
				Ttmp1 = mrotz(Ttmp1, font_l_rotz);
				break;
			}
			Trend = Ttmp1;
			render_mesh(&render, font_mesh[i]);
		}

		/* 一周目は残りフレームを表示、二周目以降は一周目に計測したフレームレートを表示。 */
		j = render.bottom;
		render.bottom = DISP_Y;
		if(!loop) {
			frame++;
			i = (FSEC(15,10)-FSEC(4,10)) - frame;
			render_printf(&render, 114, 81, 0x82, 0, "%3d", i);
			dT = pceTimerGetCount() - T0;
		} else {
			i = frame * (1000 * 10) / dT;
			if(i > 999) {
				i = 999;
			}
			render_printf(&render, 112, 81, 0x82, 0, "%2d", i / 10);
			render_point(&render, 120, 85, 0);
			render_printf(&render, 122, 81, 0x82, 0, "%d", i % 10);
		}
		render.bottom = j;

		/* 進行、ループ。 */
		step++;
		if(step >= FSEC(15,10)-FSEC(4,10)) {
			step = 0;
			loop++;
		}
	}
}
