/************************************************************
  Ninja Sample
  COPYRIGHT (C) SEGA ENTERPRISES, LTD.

  タイトル ： 物体に影をつける（シャドウボリュウムでは無い）
              shadow ( but it's not shadow volume )
  編集履歴 ： 98-04-28 製作開始
              98-06-19 SET5対応
              99-04-05 appends the English comments
************************************************************/

#include <shinobi.h>
#include <njdef.h>

void main(void)
{
#ifdef __GNUC__
	shinobi_workaround();
#endif
    njSetBorderColor( 0x00000000 );
    njUserInit();
    while (njUserMain() == NJD_USER_CONTINUE) {
        njWaitVSync();
    }
    njUserExit();
}


/* 外部定義モデルデータ */
/* external declaration of model data */
extern NJS_OBJECT object_logo[];
extern NJS_OBJECT object_arrow[];

/* グローバルデータセクション */
/* global data */
#define VERTEX_MAX      4096
#define MATRIX_DEPTH    12

/* 3D関連データ */
/* 3D data */
static NJS_MATRIX     *g_Matrix;
static NJS_VERTEX_BUF *g_VBuf;

static NJS_VIEW    g_View;
static NJS_LIGHT   g_Light;

/* 光源のデータ */
/* light source */
static NJS_POINT3  g_LightDir;
static NJS_POINT3  g_LightPos;
static Angle       g_LightTheta;
static Angle       g_LightPhai;
static float       g_LightR;

/* 地面の描画 */
static float g_GroundHeight = -3.0F;

/* 関数のプロトタイプ */
/* prototype declaration */
void DrawPlane(float size);
void LightPhase();
void ObjectPhase();
void ShadowPhase();

/* ポーズ判定フラグ */
/* pause flag       */
Bool g_bPause = FALSE;

/* ポーズ時処理関数 */
/* pause function   */
void PausePhase(void);

/* アクティブ時処理関数 */
/* active (not pause) function */
void ActivePhase(void);

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

	njSetBorderColor( 0x00000000 );
    njUserInit --- Ninjaシステム初期化関数

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

void njUserInit(void)
{
    /* 画面モードの設定 */
    /* Initializes the Dreamcast H/W and readies the SEGA library */
    sbInitSystem( NJD_RESOLUTION_640x480_NTSCI, NJD_FRAMEBUFFER_MODE_RGB565, 1);

    /* メモリの割り当て */
    /* allocates the memory */
    g_Matrix = (NJS_MATRIX *)syMalloc(sizeof(NJS_MATRIX) * MATRIX_DEPTH);
    g_VBuf = (NJS_VERTEX_BUF *)syMalloc(sizeof(NJS_VERTEX_BUF) * VERTEX_MAX);

    /* 頂点バッファの確保 */
    /* allocates the buffers for registration of vertex data */
    njInitVertexBuffer(VERTEX_MAX * 64 / 4, 0, VERTEX_MAX * 64 / 4, 0, 0);
	njInitPrint( NULL, 0, NJD_TEXFMT_ARGB_1555 );

    /* モデルビューマトリクス初期化 */
  	/* initializes the matrix stack */
    njInitMatrix(g_Matrix, MATRIX_DEPTH, 1);

    /* 演算用頂点バッファの確保 */
    /* initializes 3D system  */
    njInit3D(g_VBuf, VERTEX_MAX);

    /* ビューの初期化と設定 */
    /* initializes the user-defined view */
    njInitView(&g_View);
    njSetView(&g_View);

    /* ライトの初期化 */
    /* initializes the light source */
    g_LightTheta = NJM_DEG_ANG(45.0F);
    g_LightPhai  = NJM_DEG_ANG(45.0F);
    g_LightR = 5.0F;
    g_LightDir.x = -1.0F;
    g_LightDir.y = -1.0F;
    g_LightDir.z = -1.0F;
    njCreateLight(&g_Light, NJD_DIR_LIGHT);
    njSetLightDirection(&g_Light, g_LightDir.x, g_LightDir.y, g_LightDir.z);

    /* マテリアルを変更できるように設定 */
    /* model material data changed to a fixed value */
    njControl3D(NJD_CONTROL_3D_CONSTANT_MATERIAL);

    /* バックグラウンドのカラーを設定 */
  	/* sets background color */
    njSetBackColor(ARGB(0xff, 0xc0, 0, 0),
                            ARGB(0xff, 0, 0xc0, 0), ARGB(0xff, 0, 0, 0xc0));
    /* 画面のアスペクト比を設定 */
	/* corrects the screen aspect ratio */
    njSetAspect( 1.0F, 1.0F );

    /* テキストのカラーを設定 */
    /* sets the text color */
    njPrintColor(ARGB(0xff, 0xc0, 0xc0, 0xc0));
}


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

    njUserMain --- Ninjaシステムメイン処理関数
                   main
**************************************************************************/

Sint32 njUserMain(void)
{
    NJS_PERIPHERAL *per;

    /* ジョイスティックポート１のペリフェラル情報を取得 */
    /* gets the peripheral information */
    per = njGetPeripheral(NJD_PORT_A0);

    /* ボタンが押されたかどうかチェック */
    /* checks the start button */
    if (per->press & NJD_DGT_ST) {
        g_bPause = !g_bPause;
    }

	/* ポーズ状態によって処理を切り替える */
	/* pause or not */
	if (!g_bPause) {
		ActivePhase();
	} else {
		PausePhase();
	}

    /* デバッグ情報表示 */
    /* prints the sample program name */
    njPrintC(NJM_LOCATION(23, 6), "Ninja Sample SHADOW");

    return NJD_USER_CONTINUE;
}


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

    ActivePhase --- アクティブ時処理関数
                    active (not pause) routine
**************************************************************************/

void ActivePhase(void)
{
    static Angle theta = NJM_DEG_ANG(0.0F);

    /* マトリクスのクリア */
    /* clears the matrix stack */
    njClearMatrix();

    /* 移動 */
    /* translate */
    njTranslate(NULL, 0.0F, 0.0F, -20.0F);

    /* 回転 */
    /* rotate */
    theta += NJM_DEG_ANG(0.1F);
    njRotateY(NULL, theta);

    /* 光源の処理 */
    /* light routine */
    LightPhase();

    /* 地面の描画 */
    /* draws plane */
    DrawPlane(10.0F);

    /* オブジェクトの描画 */
	/* draws object */
    ObjectPhase();

    /* 影の処理 */
    /* shadow routine */
    ShadowPhase();
}


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

    PausePhase --- ポーズ時処理関数
                   pause routine
**************************************************************************/

void PausePhase(void)
{
    NJS_POINT2COL pc;
    NJS_POINT2    p[4];
    NJS_COLOR     col[4];
        
    /* セーバー用スプライトの設定 */
    /* sets polygon for screen saver */
    pc.p   = p;
    pc.col = col;
    pc.tex = NULL;
    pc.num = 4;
    
    p[0].x = 0;
    p[0].y = 0;
    col[0].color = 0xff000000;

    p[1].x = 0;
    p[1].y = 480;
    col[1].color = 0xff000000;

    p[2].x = 640;
    p[2].y = 480;
    col[2].color = 0xff000000;

    p[3].x = 640;
    p[3].y = 0;
    col[3].color = 0xff000000;

    /* セーバー用ポリゴンの描画 */
    /* draws the screen saver */
    njDrawPolygon2D(&pc, 4, -2.0F, NJD_FILL);

	/* ポーズのお知らせ */
	/* prints 'PAUSE' string */
    njPrintC(NJM_LOCATION(38, 30), "PAUSE");
}


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

    DrawPlane --- 平面描画関数
                  draws plane
**************************************************************************/

void DrawPlane(float size)
{
    NJS_POINT3COL p3col;
    NJS_POINT3    point[4];
    NJS_COLOR     color[4];

    /* 頂点データの作成 */
    /* sets the vertex data */
    p3col.p   = point;
    p3col.col = color;
    p3col.num = 4;
    
    point[0].x = -size;
    point[0].y =  g_GroundHeight;
    point[0].z = -size;
    color[0].argb.a = 0xff;
    color[0].argb.r = 0x88;
    color[0].argb.g = 0xff;
    color[0].argb.b = 0x88;

    point[1].x = -size;
    point[1].y =  g_GroundHeight;
    point[1].z =  size;
    color[1].argb.a = 0xff;
    color[1].argb.r = 0x88;
    color[1].argb.g = 0xff;
    color[1].argb.b = 0x88;

    point[2].x =  size;
    point[2].y =  g_GroundHeight;
    point[2].z =  size;
    color[2].argb.a = 0xff;
    color[2].argb.r = 0x88;
    color[2].argb.g = 0xff;
    color[2].argb.b = 0x88;

    point[3].x =  size;
    point[3].y =  g_GroundHeight;
    point[3].z = -size;
    color[3].argb.a = 0xff;
    color[3].argb.r = 0x88;
    color[3].argb.g = 0xff;
    color[3].argb.b = 0x88;

    /* 平面の描画 */
    /* draws the plane */
    njPushMatrix(NULL);
        njDrawPolygon3D(&p3col, 4, NJD_FILL);
    njPopMatrix(1);
}


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

    LightPhase --- 光源処理関数
                   light source routine
**************************************************************************/

void LightPhase(void)
{
    NJS_POINT3COL p3c;
    NJS_POINT3    p[50];
    NJS_COLOR     col[50];
    NJS_ARGB      argb;
    int i, j;

    /* 光源の移動 */
    /* moves the light source */
    g_LightTheta += NJM_DEG_ANG(1.0F);

    /* 光源の直交系における座標の算出 */
    /* calculate the coordinates (x,y,z) of the light source */
    g_LightPos.x = g_LightR * njCos(g_LightPhai) * njCos(g_LightTheta);
    g_LightPos.y = g_LightR * njSin(g_LightPhai);
    g_LightPos.z = g_LightR * njCos(g_LightPhai) * njSin(g_LightTheta);
    g_LightDir.x = -g_LightPos.x;
    g_LightDir.y = -g_LightPos.y;
    g_LightDir.z = -g_LightPos.z;

    /* 光源の位置＆方向を設定 */
    /* set the light postion and direction */
    njSetLightLocation(&g_Light, g_LightPos.x, g_LightPos.y, g_LightPos.z);
    njSetLightDirection(&g_Light, g_LightDir.x, g_LightDir.y, g_LightDir.z);

    /* マテリアルの設定 */
    /* sets model materials */
    argb.a = 1.0F;
    argb.r = 1.0F;
    argb.g = 1.0F;
    argb.b = 0.0F;
    njSetConstantMaterial( &argb );

    /* 光源方向矢印の描画 */
    /* draws the arrow that is light direction */
    njPushMatrix(NULL);
        njTranslate(NULL, g_LightPos.x, g_LightPos.y, g_LightPos.z);
        njRotateY(NULL, NJM_DEG_ANG(90.0F) - g_LightTheta);
        njRotateX(NULL, g_LightPhai + NJM_DEG_ANG(180.0F));
        njScale(NULL, 0.1F, 0.1F, 0.1F);
        njDrawObject(object_arrow);
    njPopMatrix(1);

    /* 光線の設定 */
    /* sets the ray of ilght */
    p3c.num = 50;
    p3c.p = p;
    p3c.col = col;

    for (j = 0; j < 5; j++) {
        for (i = 0; i < 5; i++) {
            p3c.p[i * 2 + j * 10].x = g_LightPos.x - 4.0F + i * 2;
            p3c.p[i * 2 + j * 10].y = g_LightPos.y;
            p3c.p[i * 2 + j * 10].z = g_LightPos.z - 4.0F + j * 2;
            p3c.col[i * 2 + j * 10].argb.a = 0xff;
            p3c.col[i * 2 + j * 10].argb.r = 0xff;
            p3c.col[i * 2 + j * 10].argb.g = 0xff;
            p3c.col[i * 2 + j * 10].argb.b = 0xff;
            p3c.p[i * 2 + j * 10 + 1].x = -g_LightPos.x - 4.0F + i * 2;
            p3c.p[i * 2 + j * 10 + 1].y = -g_LightPos.y;
            p3c.p[i * 2 + j * 10 + 1].z = -g_LightPos.z - 4.0F + j * 2;
            p3c.col[i * 2 + j * 10 + 1].argb.a = 0xff;
            p3c.col[i * 2 + j * 10 + 1].argb.r = 0xff;
            p3c.col[i * 2 + j * 10 + 1].argb.g = 0xff;
            p3c.col[i * 2 + j * 10 + 1].argb.b = 0xff;
        }
    }

    /* 光線の描画 */
    /* draws the ray of light */
    njDrawLine3D(&p3c, 25, NJD_DRAW_NORMAL);
}


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

    ShadowPhase --- 影処理関数

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

void ShadowPhase(void)
{
    NJS_ARGB  argb;
    NJS_MATRIX shadowMat;
    float dot;

    /* 空間上の全ての点を平面上に射影するマトリクスの算出 */
    /* 空間上の点 S(sx, sy, dz) を通り方向ベクトルが d = (dx, dy, dz) 
       の直線 L と平面 ax+by+cz+d = 0 との交点 S'(s'x, s'y, s'z)を求める
       式をたて、その式から 点S から 点S' への変換を行うマトリクスを
       求める。そのマトリクスは空間上の任意の点を平面 ax+by+cz+d = 0
       上へ直線 L により写像するマトリクスであるので、目的のマトリクス
       である。以下では計算結果にもとづきマトリクスを生成*/

    /* calculate the matrix that project all points to the plane */

    dot = 1.0F * g_LightDir.y / g_LightR;

    shadowMat[M00] = g_LightDir.y / g_LightR / dot;
    shadowMat[M10] = -g_LightDir.x / g_LightR / dot;
    shadowMat[M20] = 0.0F;
    shadowMat[M30] = (g_GroundHeight + 0.1F) * g_LightDir.x / g_LightR / dot;

    shadowMat[M01] = 0.0F;
    shadowMat[M11] = 0.0F;
    shadowMat[M21] = 0.0F;
    shadowMat[M31] = (g_GroundHeight + 0.1F) * g_LightDir.y / g_LightR / dot;

    shadowMat[M02] = 0.0F;
    shadowMat[M12] = -g_LightDir.z / g_LightR / dot;
    shadowMat[M22] = g_LightDir.y / g_LightR / dot;
    shadowMat[M32] = (g_GroundHeight + 0.1F) * g_LightDir.z / g_LightR / dot;

    shadowMat[M03] = 0.0F;
    shadowMat[M13] = 0.0F;
    shadowMat[M23] = 0.0F;
    shadowMat[M33] = 1.0F;

    /* マテリアルの設定 */
    /* sets model materials */
    argb.a = -0.5F;
    argb.r = 0.0F;
    argb.g = 0.0F;
    argb.b = 0.0F;
    njSetConstantMaterial( &argb );

    /* 描画 */
    /* draws object */
    njPushMatrix(NULL);
        njMultiMatrix(NULL, &shadowMat);
        njDrawObject(object_logo);
    njPopMatrix(1);
}


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

    ObjectPhase --- オブジェクト処理関数
                    draw object
**************************************************************************/

void ObjectPhase(void)
{
    NJS_ARGB argb;

    /* マテリアルの設定 */
    /* sets model materials */
    argb.a = 1.0F;
    argb.r = 0.5F;
    argb.g = 1.0F;
    argb.b = 0.5F;
    njSetConstantMaterial( &argb );

    /* オブジェクトの描画 */
    /* draws object */
    njPushMatrix(NULL);
        njDrawObject(object_logo);
    njPopMatrix(1);
}


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

    njUserExit --- Ninjaシステム終了処理関数
                   exit
**************************************************************************/

void njUserExit(void)
{
	njExitPrint();

    /* メモリの開放 */
	/* frees the memory area */
    syFree(g_Matrix);
    syFree(g_VBuf);

    /* システム終了処理関数 */
    /* exits the system    */
    sbExitSystem();
}
