/*
 *	cliprods.h
 *
 *	ROaD STRip
 *
 *	* Fri Jun 09 23:00:51 JST 2017 Naoyuki Sawa
 *	- 1st リリース。
 *	- cliprods.cは、cliprodg.cの上位モジュールであり、cliprodg.cと組み合わせて使用する事を想定しています。
 *	  ただし、cliprodg.cに依存してはいないので、任意の描画バックエンドと組み合わせて使用する事も可能です。
 *	  組み合わせる描画バックエンドが、(少なくともスキャンライン精度の)クリッピング機能を持っている事が望ましいですが、
 *	  もしクリッピング機能を持っていなくても、ペインタアルゴリズムによって同じ描画結果を得られるように作ってあります。
 *	  クリッピング機能の有無を通知する必要は無く、クリッピング機能を使用すれば無駄な描画が省けて自動的に高速化します。
 *	* Sun Jun 18 21:40:53 JST 2017 Naoyuki Sawa
 *	- アプリケーションだけが参照する情報を、当モジュールの構造体定義から外しました。
 *	  具体的には、以下の通りです。
 *	  ・これまで、ST_RodObj.objはアプリケーションだけが参照し、当モジュールは関与しない情報だったので、当モジュールの定義から外して、新たにST_RodObjBase構造体として定義しました。
 *	    今後は、アプリケーションがST_RodObjBase構造体を内包したST_RodObj構造体(名前は任意です)を定義して、その中にobjに相当するフィールドを定義して下さい。
 *	  ・これまで、ST_RodSeg.roadを当モジュールが解釈してiRoad1,iRoad2に展開していましたが、アプリケーションが処理する方が適切なので、当モジュールの定義から外して、新たにST_RodSegBase構造体として定義しました。
 *	    今後は、アプリケーションがST_RodSegBase構造体を内包したST_RodSeg構造体(名前は任意です)を定義して、その中にroadに相当するフィールドを定義して下さい。
 *	    ST_RodSeg.roadを当モジュールが解釈しなくなった事に伴い、ST_RodSeg.TBL_RodPatも不要になったので、当モジュールの定義から外しました。
 *	- アプリケーション定義のfnDrawObj関数に、オブジェクトのY軸回転を計算するための引数を追加しました。
 *	  具体的には、cliprods.c内の「RodStr_Draw()の使用例」を参照して下さい。
 *	- 今回は大幅な変更だったので、変更前のコードはコメントアウトして残していません。
 *	  変更前のコードを参照する必要が生じた場合は、前回のアーカイブを参照して下さい。
 *	* Wed Jun 21 22:55:18 JST 2017 Naoyuki Sawa
 *	- fnDrawObj関数の引数に、pRodSegBaseを追加しました。
 *	  オブジェクトが属するセグメントはfnDrawObj関数側にて'&pRodStr->TBL_RodSegBase[(pRodObjBase->z-pRodStr->scrZ+pRodStr->segOfs)/pRodStr->segLen]'で求める事も出来るのですが、以下の理由により引数として渡す事にしました。
 *	  理由1. オブジェクトを描画するために、オブジェクトが属するセグメントを求める機会が多く、その都度上記の式を使って求めるのは、実装の手間も処理の負荷も増えて望ましくないから。
 *	  理由2. RodStr_Draw()がfnDrawObj関数を呼び出す時点で、RodStr_Draw()が既に上記の式に相当するpRodSegBaseを保持しており、仮にfnDrawObj関数がpRodSegBase引数を参照せず無駄になったとしても、pRodSegBase引数を追加した事によるオーバーヘッドはほとんど無いから。
 *	* Thu Jun 22 22:38:07 JST 2017 Naoyuki Sawa
 *	- セグメントの手前にはみ出したオブジェクトの描画に対応しました。
 *	  詳細は、以下の通りです。
 *	- これまでのアルゴリズムでは、オブジェクトのスプライト原点が下端でない場合に、オブジェクトの下が欠けて描画される問題が有りました。
 *	  例えば、オブジェクトのスプライトを、少し斜め上から見たデザインにした場合は、スプライト原点は下端よりも少し上にする事が多いです。
 *	  その時、スプライト原点よりも下の部分が、そのオブジェクトが属するセグメントよりも手前のセグメントの道によって上書きされてしまい、欠けて描画されるのが原因でした。
 *	- 上記の問題を防ぐには、セグメント単位で、道の描画とオブジェクトの描画を行うのではなく、一定範囲のセグメントの道をまとめて描いた後、一定範囲のセグメントのオブジェクトを描く事です。
 *	  一定範囲とは、道の表裏が切り替わりを区切りとした範囲の事です。
 *	  具体的には、以下の通りです。
 *	  │①道の表が見えている範囲の道をまとめて描き、その後、その範囲のオブジェクトをまとめて描きます。
 *	  │②道の裏が見えている範囲の道をまとめて描き、その後、その範囲のオブジェクトをまとめて描きます。
 *	  ①と②を繰り返します。
 *	  尚、一番奥のセグメントの道が表か裏かによって、上記の①と②はどちらから開始しても構いません。
 *	  また、一番最後(=一番手前)のセグメントの道を描いた後は、残っているオブジェクトを全てまとめて描きます。
 *	- 上記の処理にて、オブジェクトの描画に使用するiLineMaxは、個々のオブジェクトが属するセグメントのiLineMaxではなく、一定範囲の一番手前のセグメントのiLineMaxを参照する必要が有ります。
 *	  なぜなら、個々のオブジェクトが属するセグメントのiLineMaxによってクリッピングすると、手前のセグメントの上書きによるスプライト欠けと同様にクリッピングによって欠けてしまうからです。
 *	  上記の一定範囲のセグメントに属するオブジェクトは、全て、(描画上は)一定範囲のセグメントの道よりも手前に有ると見なして構わないので、一定範囲の一番手前のセグメントのiLineMaxを参照すれば良いのです。
 *	- 基本的なアルゴリズムとしては、ここまでに説明した通りで良いのですが、実装を簡単にするために、少し工夫した点が有ります。
 *	  ・一点目として、②で'道の裏が見えている範囲の道をまとめて描き'と書きましたが、道の裏は実際には描画しないので、①と②はまとめて処理する事が出来ます。
 *	  ・二点目として、②の'道の裏が見えている範囲'ではiLineMaxは変化していないはずなので、①で参照する一番手前のiLineMaxと、②で参照する一番手前のiLineMaxは、同じ値であるはずです。
 *	  ・三点目として、②の範囲では道を描かないので、②の範囲を複数に分割してオブジェクトを描いても、同じ結果になるはずです。
 *	  上記三点より、アルゴリズムは以下のように単純化出来ます。
 *	  │①道の表が見えていたら、道を描いた後、オブジェクトを描画しない。ただし、最後のセグメントだった場合は、道を描いた後、オブジェクトを描画する。
 *	  │②道の裏が見えていたら、そのセグメント(含む)までのオブジェクトを描画する。その際、そのセグメントのiLineMaxを使ってクリッピングを行う。
 *	  今回変更した、RodStr_Draw()後半の描画処理は、上記の工夫した①②のアルゴリズムに従って、実装しました。
 *	- 今回の変更によって、セグメントの手前にはみ出したオブジェクトも、自然に描画出来るようになりました。
 *	  ただし、稀に、セグメントの手前にはみ出した部分が、ちらついて見える問題が発生する事が有ります。
 *	  どのような場合に発生するかと言うと、道のアップダウンの変化がなだらかな箇所で、視点によって道の表裏の切り替わりの区切りとなるセグメントが変化し易い場所がある場合です。
 *	  その区切り付近に、スプライト原点よりも下の部分が多いようなオブジェクトが存在すると、僅かな視点の変化によって、スプライト原点よりも下の部分が表示されたりされなかったりして、ちらつく事が有ります。
 *	  これは、アリゴリズムの問題ではなく、そうなるべくしてなっている挙動ですから、仕方が有りません。
 *	- 今回の変更によって、既存のアプリケーションに悪影響が生じる事は有りません。
 *	  セグメントの手前にはみ出していないオブジェクトは、これまで通り描画されるので、アプリケーションの修正も不要です。
 *	  ただし、今回の変更によって、(さほど大きくはないものの)RodStr_Draw()が少しオーバーヘッド増加を伴っています。
 *	  もし今後、セグメントの手前にはみ出したオブジェクトを使用しない事にした場合は、元の動作に戻せるよう、シンボルで切り分けられるようにしておきました。
 *	  ・cliprods.cの先頭で、RODSTR_SUPPORT_PROTRUDING_OBJECTを定義すると、今回の変更が有効になります。(=セグメントの手前にはみ出したオブジェクトも自然に描画される。)
 *	  ・cliprods.cの先頭の、RODSTR_SUPPORT_PROTRUDING_OBJECTを定義しないと、今回の変更は無効になり、変更前の動作となります。(=セグメントの手前にはみ出したオブジェクトは欠ける。)
 *	  前述の通り、今回の変更によるオーバーヘッドはさほど大きくないので、常に今回の変更を有効にしておいて問題無いと思いますが、もしオーバーヘッドを減らす必要が生じた場合は無効にする事を検討して下さい。
 *	* Sun Jun 25 22:49:21 JST 2017 Naoyuki Sawa
 *	- fnDrawObj関数の引数vxにも、手前の辺と奥の辺の間で補間した値を渡すように変更しました。
 *	  変更した理由は以下の通りです。
 *	- これまでは、fnDrawObj関数の引数vxには、当該オブジェクトのセグメント内の位置に関係無く、そのセグメントまでの曲率を全て加算した値を引き渡していました。
 *	  しかしそれが原因で、vxによってオブジェクトの傾きを求めた時に不自然に細かくぶれてしまい、オブジェクトの向きがぶれて描画される問題が生じました。
 *	  なぜなら、先頭のセグメントは投影面でのクリッピングによって加算する曲率が変化しますが、それ以降のセグメントで加算する曲率は変化しないからです。
 *	  自車が前方へ移動すると、segOfsの増加に伴って先頭のセグメントから加算する曲率は減って行きますが、それ以降のセグメントで加算する曲率は変化しません。
 *	  そのため、segOfsに伴ってvxが大きな値から小さい値へ減少して行き、segOfsが0に戻ると同時にvxが大きな値に戻るという繰り返しが発生していたからです。
 *	- 上記の問題を解決するには、当該オブジェクトのセグメント内の位置によって、属するセグメントから引数vxに加算する曲率を補間すれば良いのです。
 *	  そうすれば、segOfsの増加に伴って先頭のセグメントから加算する曲率は減って行きますが、属するセグメントから加算する値は増えて行って相殺するからです。
 *	  今回、上記の変更によって問題が解決し、オブジェクトの向きがぶれなくなりました。
 *	- ただし、今回解決したのはオブジェクトの向きのぶれだけであり、オブジェクトの位置のぶれは別の問題なので未解決です。
 *	  オブジェクトの位置のぶれの問題については、RodStr_Draw()内の「←この方法は取らない事にした。下記のコメントを参照せよ。」のコメントを参照して下さい。
 */
#ifndef __CLIP_RODS_H__
#define __CLIP_RODS_H__
#ifdef  __cplusplus
extern "C" {
#endif//__cplusplus
//*****************************************************************************
//	定数
//*****************************************************************************
#ifdef  WIN32	//Win32
#define RodStr_segNum_Max	240/*調整可*/		//ST_RodStr.segNumの最大値
#else //WIN32	//P/ECE,Generic
#define RodStr_segNum_Max	 24/*調整可*/		//ST_RodStr.segNumの最大値	RodStr_Draw()のスタック消費量を減らすために少なめにした。実際の所24も有れば描画距離は充分だと思います。
#endif//WIN32
//*****************************************************************************
//	構造体
//*****************************************************************************
//ROaD SEGment BASE class
typedef struct _ST_RodSegBase {
	 int8_t			curve;						//横方向の曲率						-128～-1=左カーブ,0=直進,1～127=右カーブ
	 int8_t			slope;						//縦方向の傾き						-128～-1=下り坂,0=直進,1～127=上り坂
} ST_RodSegBase;
//-----------------------------------------------------------------------------
//ROaD OBJect BASE class
typedef struct _ST_RodObjBase {
	 int16_t		x, y;						//オブジェクトの位置		論理座標系		オブジェクトが属するセグメント(zで判断する)の道の中心に対する相対座標
	 int16_t		z;						//オブジェクトの深度		論理座標系		カメラからの距離
} ST_RodObjBase;
//-----------------------------------------------------------------------------
//ROaD STRip
typedef struct _ST_RodStr {							//							--- 制約 ---				--- 一般的な使用方法 ---			--- 備考 ---
	//--- function ---
	void (*fnDrawSeg)(const struct _ST_RodStr* pRodStr,			//セグメント描画関数					NULL不可				固定						┬アプリケーション定義の関数は、一般的にはRodGen_DrawSeg()を呼び出すだけです。
	                  const ST_RodSegBase* pRodSegBase,			//																		│又は、別の描画バックエンドを使う場合は、RodGen_DrawSeg()を使わずに独自に実装しても構いません。
	                  int iLine1/*含む*/,   int x11, int x21,		//																		│その際、引数iLineMaxによるクリッピングは行わなくても構いません。
	                  int iLine2/*含まず*/, int x12, int x22,		//																		│もしfnDrawSeg()がクリッピングを行わなくても、ペインタアルゴリズムだけで同じ描画結果が得られるよう、RodStr_Draw()が奥から手前の順でfnDrawSeg()を呼び出すからです。
	                  int iLineMax/*含む*/);				//																		┘とは言え、クリッピングを行う方が効率は良くなるので、描画バックエンドがクリッピング機能を持っているならば、なるべくクリッピングも行うようにして下さい。
//{{2017/06/21変更:fnDrawObj関数の引数に、pRodSegBaseを追加しました。
//	void (*fnDrawObj)(const struct _ST_RodStr* pRodStr,			//オブジェクト描画関数					NULL可					固定						┬引数iLineMaxによるクリッピングは行わなくても構いません。fnDrawSeg()の説明と同様です。
//	                  const ST_RodObjBase* pRodObjBase,			//																		│単純な描画を行う場合は引数x,y(デバイス座標系)、及び、拡大縮小を表現するためにpRodObjBase->z(論理座標系)を参照するだけで構いません。
//	                  int x, int y, int dx, int vx,				//																		│引数dxはカメラに対するこのオブジェクトの相対座標(論理座標系)です。┬オブジェクトのY軸回転を表現する場合はこの引数も参照して下さい。
//	                  int iLineMax/*含む*/);				//																		┘引数vxはこのオブジェクトが属するセグメントの傾き(論理座標系)です。┘
//↓2017/06/21変更:fnDrawObj関数の引数に、pRodSegBaseを追加しました。
	void (*fnDrawObj)(const struct _ST_RodStr* pRodStr,			//オブジェクト描画関数					NULL可					固定						┬引数iLineMaxによるクリッピングは行わなくても構いません。fnDrawSeg()の説明と同様です。
	                  const ST_RodSegBase* pRodSegBase,			//																		│単純な描画を行う場合は引数x,y(デバイス座標系)、及び、拡大縮小を表現するためにpRodObjBase->z(論理座標系)を参照するだけで構いません。
	                  const ST_RodObjBase* pRodObjBase,			//																		│引数dxはカメラに対するこのオブジェクトの相対座標(論理座標系)です。┬オブジェクトのY軸回転を表現する場合はこの引数も参照して下さい。
	                  int x, int y, int dx, int vx,				//																		│引数vxはこのオブジェクトが属するセグメントの傾き(論理座標系)です。┘
	                  int iLineMax/*含む*/);				//																		┘
//}}2017/06/21変更:fnDrawObj関数の引数に、pRodSegBaseを追加しました。
	void (*fnDrawBgd)(const struct _ST_RodStr* pRodStr,			//背景描画関数						NULL可					固定						┬引数iLineMaxによるクリッピングは行わなくても構いません。fnDrawSeg()の説明と同様です。
	                  int iLineMax/*含む*/);				//																		┘
	//--- required ---
	const ST_RodSegBase*	TBL_RodSegBase/*[segNum]*/;			//セグメント配列の最初の要素のアドレス			NULL不可				内容を変更する,又は,アドレスをずらす。		セグメント配列は、カメラの深度(0)を起点とするのではなく、投影面の深度(scrZ)を起点とする事に注意して下さい。詳細は、この下のsegOfsの備考を参照して下さい。
	uint16_t		segStride;					//セグメント配列の次の要素へのストライド[byte]		sizeof(ST_RodSegBase)≦segStride	固定
	uint16_t		segNum;						//セグメント数						1≦segNum≦RodStr_segNum_Max		固定						このパラメータによって、描画距離が決定します。
	uint16_t		segLen;						//セグメント長			論理座標系		1≦segLen				固定
	uint16_t		segOfs;						//セグメントオフセット		論理座標系		0≦segOfs＜segLen			毎フレーム変更する。				(segOfs==0)の時に、最初のセグメントの手前の辺の深度が、投影面の深度(scrZ)に一致します。カメラの深度(0)に一致するのではない事に注意して下さい。segOfsを増やす事によって、最初のセグメントの手前の辺の深度が手前へ移動し、最小で(scrZ-segLen+1)となります。言い換えると、常に(最初のセグメントの手前の辺の深度≦投影面の深度＜最初のセグメントの奥の辺の深度)が成立しています。
	uint16_t		roadW;						//道の幅			論理座標系		1≦roadW				固定,又は,ステージによって変更する。
	 int16_t		camX;						//カメラの横座標		論理座標系		任意					毎フレーム変更する。				一般的にはプレイヤーの横座標(=道の中心から相対)と同じ値にする。
	uint16_t		camY;						//カメラの高さ			論理座標系		1≦camY					固定,又は,演出によって変更する。
	uint16_t		scrZ;						//カメラから投影面の距離	論理座標系		1≦scrZ					固定
	uint16_t		scrW;						//投影面の幅			論理座標系		1≦scrW					固定
	uint16_t		scrH;						//投影面の高さ			論理座標系		1≦scrH					固定
	uint16_t		dispW;						//描画面の幅			デバイス座標系		1≦dispW				固定
	uint16_t		dispH;						//描画面の高さ			デバイス座標系		1≦dispH				固定
	 int16_t		dispC;						//描画面の横方向中心座標	デバイス座標系		任意					固定,又は,演出によって変更する。		一般的には(dispW*0.5)ですが、左を多く見せたい場合は(dispW*0.7)等，右を多く見せたい場合は(dispW*0.3)等に調整して下さい。
	 int16_t		dispM;						//描画面の縦方向中心座標	デバイス座標系		任意					固定,又は,演出によって変更する。		一般的には(dispH*0.5)ですが、空を多く見せたい場合は(dispH*0.7)等，道を多く見せたい場合は(dispH*0.3)等に調整して下さい。
	//--- optional ---
	const ST_RodObjBase*	TBL_RodObjBase/*[objNum]*/;			//オブジェクト配列の最初の要素のアドレス		NULL不可				内容を変更する。				┐TBL_RodObjBaseの内容は予め呼び出し側にて深度(z)の昇順(手前から奥の順)にソートしておいて下さい。ソートされていない場合の結果は未定義です。尚、深度が投影面未満,又は,描画距離以上のオブジェクトは描画されません。
	uint16_t		objStride;					//オブジェクト配列の次の要素へのストライド[byte]	sizeof(ST_RodObjBase)≦objStride	固定						├(fnDrawObj==NULL)を指定した場合は、{TBL_RodObjBase,objStride,objNum}には全て0を指定して下さい。
	uint16_t		objNum;						//オブジェクト数					任意					毎フレーム変更する。				┘
} ST_RodStr;
/****************************************************************************
 *	グローバル関数
 ****************************************************************************/
void RodStr_Draw(const ST_RodStr* pRodStr);
#ifdef  __cplusplus
}//extern "C"
#endif//__cplusplus
#endif//__CLIP_RODS_H__
