/*
 *	cliprods.c
 *
 *	ROaD STRip
 *
 *	CLiP - Common Library for P/ECE
 *	Copyright (C) 2017 Naoyuki Sawa
 *
 *	* 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()内の「手前の辺と奥の辺の間で補間する。」の右側のコメントを参照して下さい。
 */
#include "clip.h"
//{{2017/06/22追加:セグメントの手前にはみ出したオブジェクトの描画に対応しました。
#define RODSTR_SUPPORT_PROTRUDING_OBJECT	//このシンボルを定義すると、セグメントの手前にはみ出したオブジェクトも自然に描画される。
//}}2017/06/22追加:セグメントの手前にはみ出したオブジェクトの描画に対応しました。
//*****************************************************************************
//	ローカル関数
//*****************************************************************************
//ST_RodStr構造体を検査する。
//[in]
//	pRodStr			検査するST_RodStr構造体。
static void RodStr_Verify(const ST_RodStr* pRodStr) {
										//--- function ---
	if(!pRodStr->fnDrawSeg ||						//fnDrawSeg
										//fnDrawObj
										//fnDrawBgd
										//--- required ---
	   !pRodStr->TBL_RodSegBase ||						//TBL_RodSegBase
	   (pRodStr->segStride < sizeof(ST_RodSegBase)) ||			//segStride
	  ((pRodStr->segNum < 1) || (pRodStr->segNum > RodStr_segNum_Max)) ||	//segNum
	   (pRodStr->segLen < 1) ||						//segLen
	   (pRodStr->segOfs >= pRodStr->segLen) ||				//segOfs
	   (pRodStr->roadW < 1) ||						//roadW
										//camX
	   (pRodStr->camY < 1) ||						//camY
	   (pRodStr->scrZ < 1) ||						//scrZ
	   (pRodStr->scrW < 1) ||						//scrW
	   (pRodStr->scrH < 1) ||						//scrH
	   (pRodStr->dispW < 1) ||						//dispW
	   (pRodStr->dispH < 1)) { DIE(); }					//dispH
										//dispC
										//dispM
										//--- optional ---
	if(pRodStr->fnDrawObj) {
	  if(!pRodStr->TBL_RodObjBase ||					//TBL_RodObjBase
	     (pRodStr->objStride < sizeof(ST_RodObjBase))) { DIE(); }		//objStride
										//objNum
	} else {
	  if( pRodStr->TBL_RodObjBase ||					//TBL_RodObjBase
	      pRodStr->objStride ||						//objStride
	      pRodStr->objNum) { DIE(); }					//objNum
	}
}
//*****************************************************************************
//	グローバル関数
//*****************************************************************************
//道のストリップを描画する。
//[in]
//	pRodStr			描画するST_RodStr構造体。
void RodStr_Draw(const ST_RodStr* pRodStr) {
	typedef struct _ST_RodInf {		//ROaD segment INFormation
		 int16_t	x, y, z;	//このセグメントの手前の辺の位置。最初のセグメントの場合、'手前の辺'は投影面でクリッピングした辺を意味する。				論理座標系
//{{2017/06/25変更:fnDrawObj関数の引数vxにも、手前の辺と奥の辺の間で補間した値を渡すように変更しました。
//		 int16_t	vx;		//このセグメントの横方向の傾き。最初のセグメントの傾きは、投影面でクリッピングした辺よりも奥の分だけとする。				論理座標系
//↓2017/06/25変更:fnDrawObj関数の引数vxにも、手前の辺と奥の辺の間で補間した値を渡すように変更しました。
		 int16_t	curve;		//このセグメントの横方向の傾き。最初のセグメントの傾きは、投影面でクリッピングした辺よりも奥の分だけとする。				論理座標系
//}}2017/06/25変更:fnDrawObj関数の引数vxにも、手前の辺と奥の辺の間で補間した値を渡すように変更しました。
		 int16_t	xL, xR, yC;	//このセグメントの描画情報(手前の辺の道の左端のピクセル座標，手前の辺の道の右端のピクセル座標、手前の辺のライン番号)。			デバイス座標系
		 int16_t	iLineMax;	//このセグメント,及び,このセグメントに含まれるオブジェクトを描画する時の、描画を許可する最大のライン番号(このライン番号も含む)。	デバイス座標系		(iLineMax<0)にも成り得る事に注意せよ。描画を許可する範囲が無い事を意味する。
	} ST_RodInf;
	ST_RodInf TBL_RodInf[RodStr_segNum_Max], *pRodInf;
	const ST_RodSegBase* pRodSegBase;
	const ST_RodObjBase* pRodObjBase;
	int iSeg, iObj, iLineMax, vx, curve, slope;
	int x1, y1, z1, x1L, x1R, y1C;
	int x2, y2, z2, x2L, x2R, y2C;
	//引数を検査する。
	RodStr_Verify(pRodStr);
	//描画を許可する最大のライン番号(このライン番号も含む)を初期化する。
	iLineMax = pRodStr->dispH - 1;
	//手前の辺の位置を初期化する。
	vx = 0;			//横方向の傾き(=投影面の直下からの、横方向の曲率の合計)
	x1 = -pRodStr->camX;	//手前の辺の中心の横位置	論理座標系	┐
	y1 = -pRodStr->camY;	//手前の辺の縦位置		論理座標系	├最初のセグメントの場合、'手前の辺'は投影面でクリッピングした辺を意味する。
	z1 =  pRodStr->scrZ;	//手前の辺の深度		論理座標系	┘
	//手前の辺の透視変換とビューポート変換を行う。
	x1L = x1 - (pRodStr->roadW >> 1);
	x1R = x1L + pRodStr->roadW;
	y1C = y1;
	x1L = pRodStr->dispC + (x1L * pRodStr->scrZ * pRodStr->dispW) / (z1 * pRodStr->scrW);	//オーバーフローに注意!!
	x1R = pRodStr->dispC + (x1R * pRodStr->scrZ * pRodStr->dispW) / (z1 * pRodStr->scrW);	//オーバーフローに注意!!
	y1C = pRodStr->dispM - (y1C * pRodStr->scrZ * pRodStr->dispH) / (z1 * pRodStr->scrH);	//オーバーフローに注意!!
	//全てのセグメントを手前から奥の順で走査して…
	pRodInf     =          TBL_RodInf;
	pRodSegBase = pRodStr->TBL_RodSegBase;
	for(iSeg = 0; iSeg < pRodStr->segNum; iSeg++) {
		//このセグメントの、横方向の曲率と縦方向の傾きを取得する。
		curve = pRodSegBase->curve;
		slope = pRodSegBase->slope;
		//最初のセグメントならば…
		if(!iSeg) {
			//投影面よりも手前の部分を除いた、横方向の曲率と縦方向の傾きを求める。
			curve = curve * (pRodStr->segLen - pRodStr->segOfs) / pRodStr->segLen;
			slope = slope * (pRodStr->segLen - pRodStr->segOfs) / pRodStr->segLen;
		}
//{{2017/06/25追加:fnDrawObj関数の引数vxにも、手前の辺と奥の辺の間で補間した値を渡すように変更しました。
		//セグメント情報を格納する。
		pRodInf->x = x1;
		pRodInf->y = y1;
		pRodInf->z = z1;
		pRodInf->curve = curve;
		pRodInf->xL = x1L;
		pRodInf->xR = x1R;
		pRodInf->yC = y1C;
		pRodInf->iLineMax = iLineMax;	//この下でiLineMaxを更新する前の値とする事に注意!!
//}}2017/06/25追加:fnDrawObj関数の引数vxにも、手前の辺と奥の辺の間で補間した値を渡すように変更しました。
		//奥の辺の位置を求める。
		vx += curve;			//横方向の傾き(=投影面の直下からの、横方向の曲率の合計)
		x2 = x1 + vx;			//奥の辺の中心の横位置		論理座標系
		y2 = y1 + slope;		//奥の辺の縦位置		論理座標系
		z2 = z1 + pRodStr->segLen;	//奥の辺の深度			論理座標系
		if(!iSeg) { z2 -= pRodStr->segOfs; }	//最初のセグメントならば、セグメントオフセットを考慮する。
//{{2017/06/25削除:fnDrawObj関数の引数vxにも、手前の辺と奥の辺の間で補間した値を渡すように変更しました。
//		//セグメント情報を格納する。
//		pRodInf->x = x1;
//		pRodInf->y = y1;
//		pRodInf->z = z1;
//		pRodInf->vx = vx;		//この上でvxを更新した後の値とする事に注意!!
//		pRodInf->xL = x1L;
//		pRodInf->xR = x1R;
//		pRodInf->yC = y1C;
//		pRodInf->iLineMax = iLineMax;	//この下でiLineMaxを更新する前の値とする事に注意!!
//}}2017/06/25削除:fnDrawObj関数の引数vxにも、手前の辺と奥の辺の間で補間した値を渡すように変更しました。
		//奥の辺の透視変換とビューポート変換を行う。
		x2L = x2 - (pRodStr->roadW >> 1);
		x2R = x2L + pRodStr->roadW;
		y2C = y2;
		x2L = pRodStr->dispC + (x2L * pRodStr->scrZ * pRodStr->dispW) / (z2 * pRodStr->scrW);	//オーバーフローに注意!!
		x2R = pRodStr->dispC + (x2R * pRodStr->scrZ * pRodStr->dispW) / (z2 * pRodStr->scrW);	//オーバーフローに注意!!
		y2C = pRodStr->dispM - (y2C * pRodStr->scrZ * pRodStr->dispH) / (z2 * pRodStr->scrH);	//オーバーフローに注意!!
		//道の表が見えていれば…
		if(y2C < y1C) {
			//一部分でも可視ならば…
			if((y1C > 0) && (y2C < iLineMax)) {
				//描画を許可する最大のライン番号を更新する。
				iLineMax = y2C;		//(iLineMax<0)にも成り得る事に注意せよ。描画を許可する範囲が無い事を意味する。
			}
		//道の裏が見えているか、又は、視線に水平ならば…
		} else {
			/** no job **/
		}
		//今回の奥の辺を、次の手前の辺とする。
		x1 = x2;
		y1 = y2;
		z1 = z2;
		x1L = x2L;
		x1R = x2R;
		y1C = y2C;
		//セグメント配列とセグメント情報のポインタを次へ進める。(ポストインクリメント)
		pRodSegBase = (const ST_RodSegBase*)((const char*)pRodSegBase + pRodStr->segStride);
		pRodInf++;
	}
	//描画を許可する範囲が有れば…	この判定を行わなくても結果は同じですが、明らかに無駄な描画を避けるために判定する事にしました。
	if(iLineMax >= 0) {
		//背景描画関数が指定されていたら…
		if(pRodStr->fnDrawBgd) {
			//背景を描画する。
			(*pRodStr->fnDrawBgd)(pRodStr, iLineMax);
		}
	}
	//最後のオブジェクトインデクス(=オブジェクト数-1)を取得する。
	iObj = pRodStr->objNum - 1;
	//描画距離よりも遠いオブジェクトを読み飛ばす。
	pRodObjBase = (const ST_RodObjBase*)((const char*)pRodStr->TBL_RodObjBase + (pRodStr->objStride * iObj));
	while((iObj >= 0) && (pRodObjBase->z >= z1)) {
		pRodObjBase = (const ST_RodObjBase*)((const char*)pRodObjBase - pRodStr->objStride);
		iObj--;
	}
//{{2017/06/22追加:セグメントの手前にはみ出したオブジェクトの描画に対応しました。
#ifndef RODSTR_SUPPORT_PROTRUDING_OBJECT
//□セグメントの手前にはみ出したオブジェクトが欠けてしまうが、オーバーヘッドの少ない実装。
//}}2017/06/22追加:セグメントの手前にはみ出したオブジェクトの描画に対応しました。
	//全てのセグメントを奥から手前の順で走査して…
//不要	pRodSegBase = (const ST_RodSegBase*)((const char*)pRodStr->TBL_RodSegBase + (pRodStr->segStride * pRodStr->segNum));	┬上のループを抜けた時に既にこうなっているのでこの処理は不要です。
//不要	pRodInf     =                                              TBL_RodInf     +                       pRodStr->segNum  ;	┘
	for(iSeg = pRodStr->segNum - 1; iSeg >= 0; iSeg--) {
		//セグメント配列とセグメント情報のポインタを前へ戻す。(プレデクリメント)
		pRodSegBase = (const ST_RodSegBase*)((const char*)pRodSegBase - pRodStr->segStride);
		pRodInf--;
		//前回の手前の辺を、今回の奥の辺とする。
		x2 = x1;
		y2 = y1;
		z2 = z1;
		x2L = x1L;
		x2R = x1R;
		y2C = y1C;
		//セグメント情報を取得する。
		x1 = pRodInf->x;
		y1 = pRodInf->y;
		z1 = pRodInf->z;
//{{2017/06/25変更:fnDrawObj関数の引数vxにも、手前の辺と奥の辺の間で補間した値を渡すように変更しました。
//		vx = pRodInf->vx;
//↓2017/06/25変更:fnDrawObj関数の引数vxにも、手前の辺と奥の辺の間で補間した値を渡すように変更しました。
		curve = pRodInf->curve;
//}}2017/06/25変更:fnDrawObj関数の引数vxにも、手前の辺と奥の辺の間で補間した値を渡すように変更しました。
		x1L = pRodInf->xL;
		x1R = pRodInf->xR;
		y1C = pRodInf->yC;
		iLineMax = pRodInf->iLineMax;		//(iLineMax<0)にも成り得る事に注意せよ。描画を許可する範囲が無い事を意味する。
		//道の表が見えていれば…
		if(y2C < y1C) {
			//一部分でも可視ならば…
			if((y1C > 0) && (y2C < iLineMax)) {
				//道のセグメントを描画する。
				(*pRodStr->fnDrawSeg)(pRodStr, pRodSegBase, y1C, x1L, x1R,
				                                            y2C, x2L, x2R, iLineMax);
			}
		//道の裏が見えているか、又は、視線に水平ならば…
		} else {
			/** no job **/
		}
//{{2017/06/25追加:fnDrawObj関数の引数vxにも、手前の辺と奥の辺の間で補間した値を渡すように変更しました。
		//このセグメントの手前の辺の、横方向の傾きを求める。
		vx -= curve;	//横方向の傾き(=投影面の直下からの、横方向の曲率の合計)
//}}2017/06/25追加:fnDrawObj関数の引数vxにも、手前の辺と奥の辺の間で補間した値を渡すように変更しました。
		//このセグメントに属するオブジェクトについて…
		while((iObj >= 0) && (pRodObjBase->z >= z1)) {
			if(pRodObjBase->z > z2) { DIE(); }	//TBL_RodObjの内容がソートされていない。
			//描画を許可する範囲が有れば…	この判定を行わなくても結果は同じですが、明らかに無駄な描画を避けるために判定する事にしました。
			if(iLineMax >= 0) {
//{{2017/06/25変更:fnDrawObj関数の引数vxにも、手前の辺と奥の辺の間で補間した値を渡すように変更しました。
//				//オブジェクトを描画する。
//				int xO = pRodObjBase->x, yO = pRodObjBase->y, zO = pRodObjBase->z;
//				xO += x1 + (x2 - x1) * (zO - z1) / (z2 - z1);	//┬手前の辺と奥の辺の間で補間する。	論理座標のスケールが小さい場合、ここで演算誤差が発生して、オブジェクトが震えてしまう。xO,yO,zOをdouble型にすれば多少緩和出来るが、処理速度の低下が(特にP/ECEでは)許容出来ない。今回は処理速度を優先して、オブジェクトが震えるのを我慢する事にした。実際の所、自車と敵車がぴったり同じ相対位置を保って並走でもしない限り、オブジェクトの震えが目立つ事は無いと思う。
//				yO += y1 + (y2 - y1) * (zO - z1) / (z2 - z1);	//┘
////{{2017/06/21変更:fnDrawObj関数の引数に、pRodSegBaseを追加しました。
////				(*pRodStr->fnDrawObj)(pRodStr, pRodObjBase,	//「if(pRodStr->fnDrawObj)」の検査は不要です。(fnDrawObj==NULL)ならば(objNum==0)なのでここへ来る事は無いからです。
////				                      pRodStr->dispC + (xO * pRodStr->scrZ * pRodStr->dispW) / (zO * pRodStr->scrW),	//オーバーフローに注意!!
////				                      pRodStr->dispM - (yO * pRodStr->scrZ * pRodStr->dispH) / (zO * pRodStr->scrH),	//オーバーフローに注意!!
////				                      xO, vx, iLineMax);
////↓2017/06/21変更:fnDrawObj関数の引数に、pRodSegBaseを追加しました。
//				(*pRodStr->fnDrawObj)(pRodStr, pRodSegBase, pRodObjBase,	//「if(pRodStr->fnDrawObj)」の検査は不要です。(fnDrawObj==NULL)ならば(objNum==0)なのでここへ来る事は無いからです。
//				                      pRodStr->dispC + (xO * pRodStr->scrZ * pRodStr->dispW) / (zO * pRodStr->scrW),	//オーバーフローに注意!!
//				                      pRodStr->dispM - (yO * pRodStr->scrZ * pRodStr->dispH) / (zO * pRodStr->scrH),	//オーバーフローに注意!!
//				                      xO, vx, iLineMax);
////}}2017/06/21変更:fnDrawObj関数の引数に、pRodSegBaseを追加しました。
//↓2017/06/25変更:fnDrawObj関数の引数vxにも、手前の辺と奥の辺の間で補間した値を渡すように変更しました。
				//オブジェクトを描画する。
				int xO = pRodObjBase->x, yO = pRodObjBase->y, zO = pRodObjBase->z, vxO;
				xO += x1 + (x2 - x1) * (zO - z1) / (z2 - z1);	//┐
				yO += y1 + (y2 - y1) * (zO - z1) / (z2 - z1);	//├手前の辺と奥の辺の間で補間する。	論理座標のスケールが小さい場合、ここで演算誤差が発生して、オブジェクトが震えてしまう。xO,yO,zOをdouble型にすれば多少緩和出来るが、処理速度の低下が(特にP/ECEでは)許容出来ない。今回は処理速度を優先して、オブジェクトが震えるのを我慢する事にした。実際の所、自車と敵車がぴったり同じ相対位置を保って並走でもしない限り、オブジェクトの震えが目立つ事は無いと思う。
				vxO = vx +   curve   * (zO - z1) / (z2 - z1);	//┘
				(*pRodStr->fnDrawObj)(pRodStr, pRodSegBase, pRodObjBase,	//「if(pRodStr->fnDrawObj)」の検査は不要です。(fnDrawObj==NULL)ならば(objNum==0)なのでここへ来る事は無いからです。
				                      pRodStr->dispC + (xO * pRodStr->scrZ * pRodStr->dispW) / (zO * pRodStr->scrW),	//オーバーフローに注意!!
				                      pRodStr->dispM - (yO * pRodStr->scrZ * pRodStr->dispH) / (zO * pRodStr->scrH),	//オーバーフローに注意!!
				                      xO, vxO, iLineMax);
//}}2017/06/25変更:fnDrawObj関数の引数vxにも、手前の辺と奥の辺の間で補間した値を渡すように変更しました。
			}
			//オブジェクト配列のポインタを前へ戻す。(ポストデクリメント)
			pRodObjBase = (const ST_RodObjBase*)((const char*)pRodObjBase - pRodStr->objStride);
			iObj--;
		}
	}
//{{2017/06/22追加:セグメントの手前にはみ出したオブジェクトの描画に対応しました。
#else //RODSTR_SUPPORT_PROTRUDING_OBJECT
//□セグメントの手前にはみ出したオブジェクトも自然に描画されるが、オーバーヘッドは少し増える実装。
	{
		//オブジェクト配列走査用のポインタを保持しておく。
		// - オブジェクト配列の走査は、セグメント配列の走査よりも、複数セグメント遅れて走査します。
		//   従って、セグメント配列走査用のポインタとは別に、オブジェクト配列走査用のポインタを保持しておく必要が有ります。
		const ST_RodSegBase* obj_pRodSegBase = pRodSegBase /*= (const ST_RodSegBase*)((const char*)pRodStr->TBL_RodSegBase + (pRodStr->segStride * pRodStr->segNum))*/;
		ST_RodInf*           obj_pRodInf     = pRodInf     /*=                                              TBL_RodInf     +                       pRodStr->segNum  */;
		//                                                 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~上のループを抜けた時に既にこうなっているのでこの処理は不要です。
		//全てのセグメントを奥から手前の順で走査して…
		for(iSeg = pRodStr->segNum - 1; iSeg >= 0; iSeg--) {
			//セグメント配列とセグメント情報のポインタを前へ戻す。(プレデクリメント)
			pRodSegBase = (const ST_RodSegBase*)((const char*)pRodSegBase - pRodStr->segStride);
			pRodInf--;
			//前回の手前の辺を、今回の奥の辺とする。
		//不要	x2 = x1;						//┐
		//不要	y2 = y1;						//├これらはオブジェクト配列走査にて扱う。
		//不要	z2 = z1;						//┘
			x2L = x1L;
			x2R = x1R;
			y2C = y1C;
			//セグメント情報を取得する。
		//不要	x1 = pRodInf->x;					//┐
		//不要	y1 = pRodInf->y;					//├これらはオブジェクト配列走査にて扱う。
		//不要	z1 = pRodInf->z;					//│
//{{2017/06/25変更:fnDrawObj関数の引数vxにも、手前の辺と奥の辺の間で補間した値を渡すように変更しました。
//		//不要	vx = pRodInf->vx;					//┘
//↓2017/06/25変更:fnDrawObj関数の引数vxにも、手前の辺と奥の辺の間で補間した値を渡すように変更しました。
		//不要	curve = pRodInf->curve;					//┘
//}}2017/06/25変更:fnDrawObj関数の引数vxにも、手前の辺と奥の辺の間で補間した値を渡すように変更しました。
			x1L = pRodInf->xL;
			x1R = pRodInf->xR;
			y1C = pRodInf->yC;
			iLineMax = pRodInf->iLineMax;					//(iLineMax<0)にも成り得る事に注意せよ。描画を許可する範囲が無い事を意味する。
			//道の表が見えていれば…
			if(y2C < y1C) {
				//一部分でも可視ならば…
				if((y1C > 0) && (y2C < iLineMax)) {
					//道のセグメントを描画する。
					(*pRodStr->fnDrawSeg)(pRodStr, pRodSegBase, y1C, x1L, x1R,
					                                            y2C, x2L, x2R, iLineMax);
				}
				//最後のセグメントの道の表が見えていた場合は、道を描いた後に、残っているオブジェクトを描画する。
				if(!iSeg) { goto L_DRAWOBJ; }
			//道の裏が見えているか、又は、視線に水平ならば…
			} else {
L_DRAWOBJ:			//オブジェクト配列走査用のセグメント位置が、セグメント配列走査用のセグメント位置に追い付くまで…
				do {
#ifdef  DEBUG
					//既に、オブジェクト配列走査用のセグメント位置が、セグメント配列走査用のセグメント位置に追い付いている,又は,追い越している事は無いはず。
					if(obj_pRodInf <= pRodInf) { DIE(); }	//もしここでエラー停止したら、当モジュールのバグです。
#endif//DEBUG
					//オブジェクト配列の走査用の、セグメント配列とセグメント情報のポインタを前へ戻す。(プレデクリメント)
					obj_pRodSegBase = (const ST_RodSegBase*)((const char*)obj_pRodSegBase - pRodStr->segStride);
					obj_pRodInf--;
					//前回の手前の辺を、今回の奥の辺とする。
					x2 = x1;
					y2 = y1;
					z2 = z1;
				//不要	x2L = x1L;				//┐
				//不要	x2R = x1R;				//├これらはセグメント配列走査にて扱う。
				//不要	y2C = y1C;				//┘
					//セグメント情報を取得する。
					x1 = obj_pRodInf->x;
					y1 = obj_pRodInf->y;
					z1 = obj_pRodInf->z;
//{{2017/06/25変更:fnDrawObj関数の引数vxにも、手前の辺と奥の辺の間で補間した値を渡すように変更しました。
//					vx = obj_pRodInf->vx;
//↓2017/06/25変更:fnDrawObj関数の引数vxにも、手前の辺と奥の辺の間で補間した値を渡すように変更しました。
					curve = obj_pRodInf->curve;
//}}2017/06/25変更:fnDrawObj関数の引数vxにも、手前の辺と奥の辺の間で補間した値を渡すように変更しました。
				//不要	x1L = obj_pRodInf->xL;			//┐
				//不要	x1R = obj_pRodInf->xR;			//├これらはセグメント配列走査にて扱う。iLineMaxについても、描画するオブジェクトが属するセグメントのiLineMaxではなく、セグメント配列走査側にて取得したiLineMax(=道の表⇒裏のエッジに対するiLineMax,又は,それに等しい裏の連続に対するiLineMax)を使って、一連のセグメントに属するオブジェクトを描画する事に注意せよ。
				//不要	y1C = obj_pRodInf->yC;			//│
				//不要	iLineMax = obj_pRodInf->iLineMax;	//┘	//(iLineMax<0)にも成り得る事に注意せよ。描画を許可する範囲が無い事を意味する。
//{{2017/06/25追加:fnDrawObj関数の引数vxにも、手前の辺と奥の辺の間で補間した値を渡すように変更しました。
					//このセグメントの手前の辺の、横方向の傾きを求める。
					vx -= curve;	//横方向の傾き(=投影面の直下からの、横方向の曲率の合計)
//}}2017/06/25追加:fnDrawObj関数の引数vxにも、手前の辺と奥の辺の間で補間した値を渡すように変更しました。
					//このセグメントに属するオブジェクトについて…
					while((iObj >= 0) && (pRodObjBase->z >= z1)) {
						if(pRodObjBase->z > z2) { DIE(); }	//TBL_RodObjの内容がソートされていない。
						//描画を許可する範囲が有れば…	この判定を行わなくても結果は同じですが、明らかに無駄な描画を避けるために判定する事にしました。
						if(iLineMax >= 0) {
//{{2017/06/25変更:fnDrawObj関数の引数vxにも、手前の辺と奥の辺の間で補間した値を渡すように変更しました。
//							//オブジェクトを描画する。
//							int xO = pRodObjBase->x, yO = pRodObjBase->y, zO = pRodObjBase->z;
//							xO += x1 + (x2 - x1) * (zO - z1) / (z2 - z1);	//┬手前の辺と奥の辺の間で補間する。	論理座標のスケールが小さい場合、ここで演算誤差が発生して、オブジェクトが震えてしまう。xO,yO,zOをdouble型にすれば多少緩和出来るが、処理速度の低下が(特にP/ECEでは)許容出来ない。今回は処理速度を優先して、オブジェクトが震えるのを我慢する事にした。実際の所、自車と敵車がぴったり同じ相対位置を保って並走でもしない限り、オブジェクトの震えが目立つ事は無いと思う。
//							yO += y1 + (y2 - y1) * (zO - z1) / (z2 - z1);	//┘
//							(*pRodStr->fnDrawObj)(pRodStr, obj_pRodSegBase, pRodObjBase,	//「if(pRodStr->fnDrawObj)」の検査は不要です。(fnDrawObj==NULL)ならば(objNum==0)なのでここへ来る事は無いからです。
//							                      pRodStr->dispC + (xO * pRodStr->scrZ * pRodStr->dispW) / (zO * pRodStr->scrW),	//オーバーフローに注意!!
//							                      pRodStr->dispM - (yO * pRodStr->scrZ * pRodStr->dispH) / (zO * pRodStr->scrH),	//オーバーフローに注意!!
//							                      xO, vx, iLineMax);
//↓2017/06/25変更:fnDrawObj関数の引数vxにも、手前の辺と奥の辺の間で補間した値を渡すように変更しました。
							//オブジェクトを描画する。
							int xO = pRodObjBase->x, yO = pRodObjBase->y, zO = pRodObjBase->z, vxO;
							xO += x1 + (x2 - x1) * (zO - z1) / (z2 - z1);	//┐
							yO += y1 + (y2 - y1) * (zO - z1) / (z2 - z1);	//├手前の辺と奥の辺の間で補間する。	論理座標のスケールが小さい場合、ここで演算誤差が発生して、オブジェクトが震えてしまう。xO,yO,zOをdouble型にすれば多少緩和出来るが、処理速度の低下が(特にP/ECEでは)許容出来ない。今回は処理速度を優先して、オブジェクトが震えるのを我慢する事にした。実際の所、自車と敵車がぴったり同じ相対位置を保って並走でもしない限り、オブジェクトの震えが目立つ事は無いと思う。
							vxO = vx +   curve   * (zO - z1) / (z2 - z1);	//┘
							(*pRodStr->fnDrawObj)(pRodStr, obj_pRodSegBase, pRodObjBase,	//「if(pRodStr->fnDrawObj)」の検査は不要です。(fnDrawObj==NULL)ならば(objNum==0)なのでここへ来る事は無いからです。
							                      pRodStr->dispC + (xO * pRodStr->scrZ * pRodStr->dispW) / (zO * pRodStr->scrW),	//オーバーフローに注意!!
							                      pRodStr->dispM - (yO * pRodStr->scrZ * pRodStr->dispH) / (zO * pRodStr->scrH),	//オーバーフローに注意!!
							                      xO, vxO, iLineMax);
//}}2017/06/25変更:fnDrawObj関数の引数vxにも、手前の辺と奥の辺の間で補間した値を渡すように変更しました。
						}
						//オブジェクト配列のポインタを前へ戻す。(ポストデクリメント)
						pRodObjBase = (const ST_RodObjBase*)((const char*)pRodObjBase - pRodStr->objStride);
						iObj--;
					}
				} while(obj_pRodInf != pRodInf);	//オブジェクト配列走査用のセグメント位置が、セグメント配列走査用のセグメント位置に追い付いたら、抜ける。
			}
		}
#ifdef  DEBUG
		//最後に、オブジェクト配列走査用のセグメント位置が、セグメント配列走査用のセグメント位置に追い付いているはず。
		if(obj_pRodInf != pRodInf) { DIE(); }	//もしここでエラー停止したら、当モジュールのバグです。
#endif//DEBUG
	}
#endif//RODSTR_SUPPORT_PROTRUDING_OBJECT
//}}2017/06/22追加:セグメントの手前にはみ出したオブジェクトの描画に対応しました。
}
//*****************************************************************************
//	RodStr_Draw()の使用例
//*****************************************************************************
#if 0
#ifdef  PIECE
#define VRAM_W DISP_X
#define VRAM_H DISP_Y
#endif//PIECE
#ifdef  WIN32
#define VRAM_W (128*5)
#define VRAM_H ( 88*5)
uint8_t vbuff[VRAM_W * VRAM_H];
void update();
#endif//WIN32
int target_curve, wait_curve;
int target_slope, wait_slope;
void fnDrawSeg(const struct _ST_RodStr* pRodStr, const ST_RodSegBase* pRodSegBase, int iLine1/*含む*/, int x11, int x21, int iLine2/*含まず*/, int x12, int x22, int iLineMax/*含む*/);
//{{2017/06/21変更:fnDrawObj関数の引数に、pRodSegBaseを追加しました。
//void fnDrawObj(const struct _ST_RodStr* pRodStr, const ST_RodObjBase* pRodObjBase, int x, int y, int dx, int vx, int iLineMax/*含む*/);
//↓2017/06/21変更:fnDrawObj関数の引数に、pRodSegBaseを追加しました。
void fnDrawObj(const struct _ST_RodStr* pRodStr, const ST_RodSegBase* pRodSegBase, const ST_RodObjBase* pRodObjBase, int x, int y, int dx, int vx, int iLineMax/*含む*/);
//}}2017/06/21変更:fnDrawObj関数の引数に、pRodSegBaseを追加しました。
void fnDrawBgd(const struct _ST_RodStr* pRodStr, int iLineMax/*含む*/);
ST_RodGen stRodGen;
//□道のパターン
const uint8_t rgfx[][15]={{2,0,0,0,0,0,0,0,0,0,0,0,0,0,2},	//┐
                          {2,3,0,0,0,0,0,0,0,0,0,0,0,3,2},	//├ST_RodSeg.road=0
                          {2,3,0,0,0,0,0,0,0,0,0,0,0,3,2},	//│
                          {2,0,0,0,0,0,0,0,0,0,0,0,0,0,2},	//┘
                          {3,0,1,1,1,1,1,1,1,1,1,1,1,0,3},	//┐
                          {3,3,1,1,1,1,1,0,1,1,1,1,1,3,3},	//├ST_RodSeg.road=1
                          {3,3,1,1,1,1,1,0,1,1,1,1,1,3,3},	//│
                          {3,0,1,1,1,1,1,1,1,1,1,1,1,0,3}};	//┘
//□オブジェクト
typedef struct _ST_RodObj {
	ST_RodObjBase	base;
	uint8_t		color;
} ST_RodObj;
ST_RodObj TBL_RodObj[10/*調整可*/];
int RodObj_compar(const void* x, const void* y) { return ((ST_RodObj*)x)->base.z - ((ST_RodObj*)y)->base.z; }
//□道のストリップ
typedef struct _ST_RodSeg {
	ST_RodSegBase	base;
	uint16_t	road;
} ST_RodSeg;
ST_RodSeg TBL_RodSeg[20/*調整可*/];
ST_RodStr stRodStr = {
											//--- function ---
	fnDrawSeg,									//fnDrawSeg
	fnDrawObj,									//fnDrawObj
	fnDrawBgd,									//fnDrawBgd
											//--- required ---
	&TBL_RodSeg->base,								//TBL_RodSeg
	sizeof(ST_RodSeg),								//segStride
	ARRAY_SIZE(TBL_RodSeg),								//segNum
	16,										//segLen
	0,										//segOfs
	160,										//roadW
	0,										//camX
	44,										//camY
	8,										//scrZ
	96,										//scrW
	66,										//scrH
	VRAM_W,										//dispW
	VRAM_H,										//dispH
	VRAM_W*0.5,									//dispC
	VRAM_H*0.4,									//dispM
											//--- optional ---
	&TBL_RodObj->base,								//TBL_RodObj
	sizeof(ST_RodObj),								//objStride
	ARRAY_SIZE(TBL_RodObj),								//objNum
};
//□テストルーチンの共通部分
void test() {
	int i;
	for(i = 0; i < ARRAY_SIZE(TBL_RodSeg); i++) { TBL_RodSeg[i].road = i & 1; }
	for(i = 0; i < ARRAY_SIZE(TBL_RodObj); i++) {
		TBL_RodObj[i].base.x = RND32_RANGE(seed, -stRodStr.roadW, stRodStr.roadW);
		TBL_RodObj[i].base.z = stRodStr.segLen * i * ARRAY_SIZE(TBL_RodSeg) / ARRAY_SIZE(TBL_RodObj);
		TBL_RodObj[i].color = RND32_RANGE(seed, 0, 4);
	}
	RodGen_Init(&stRodGen, vbuff, VRAM_W, VRAM_W, rgfx[0], sizeof rgfx[0], sizeof rgfx[0]);
	for(;;) {
		const int     spd = 6/*調整可*/;
		const int enm_spd = 1/*調整可*/;
		schedule();
		stRodStr.segOfs += spd;
		while(stRodStr.segOfs >= stRodStr.segLen) {
			ST_RodSeg* pRodSeg = TBL_RodSeg;
			for(i = 0; i < ARRAY_SIZE(TBL_RodSeg) - 1; i++) {
				*pRodSeg = *(pRodSeg + 1);
				pRodSeg++;
			}
			pRodSeg->road = (pRodSeg - 1)->road ^ 1;
			if(pRodSeg->base.curve < target_curve) {
				if((pRodSeg->base.curve += 1) > target_curve) { pRodSeg->base.curve = target_curve; }
			} else if(pRodSeg->base.curve > target_curve) {
				if((pRodSeg->base.curve -= 1) < target_curve) { pRodSeg->base.curve = target_curve; }
			} else {
				if(--wait_curve <= 0) {
					wait_curve = RND32_RANGE(seed, 0, 30);
					target_curve = RND32_RANGE(seed, -31, 32);
				}
			}
			if(pRodSeg->base.slope < target_slope) {
				if((pRodSeg->base.slope += 4) > target_slope) { pRodSeg->base.slope = target_slope; }
			} else if(pRodSeg->base.slope > target_slope) {
				if((pRodSeg->base.slope -= 4) < target_slope) { pRodSeg->base.slope = target_slope; }
			} else {
				if(--wait_slope <= 0) {
					wait_slope = RND32_RANGE(seed, 0, 15);
					target_slope = RND32_RANGE(seed, -63, 64);
				}
			}
			stRodStr.segOfs -= stRodStr.segLen;
		}
		for(i = 0; i < ARRAY_SIZE(TBL_RodObj); i++) {
			if(abs(TBL_RodObj[i].base.x) >= ((stRodStr.roadW * (15 - 2)) >> 5)) {
				TBL_RodObj[i].base.z -=     spd;	//木等を想定
			} else {
				TBL_RodObj[i].base.z -= enm_spd;	//敵車を想定
			}
			if(TBL_RodObj[i].base.z < 0) {
				TBL_RodObj[i].base.x = RND32_RANGE(seed, -stRodStr.roadW, stRodStr.roadW);
				TBL_RodObj[i].base.z += stRodStr.segLen * ARRAY_SIZE(TBL_RodSeg);
				TBL_RodObj[i].color = RND32_RANGE(seed, 0, 4);
			}
		}
		qsort(TBL_RodObj, ARRAY_SIZE(TBL_RodObj), sizeof TBL_RodObj[0], RodObj_compar);
		stRodStr.camX = TBL_RodSeg[0].base.curve * 1/*調整可*/;
		RodStr_Draw(&stRodStr);
#ifdef  WIN32
		update();
#endif//WIN32
	}
}
void fnDrawSeg(const struct _ST_RodStr* pRodStr, const ST_RodSegBase* pRodSegBase, int iLine1/*含む*/, int x11, int x21, int iLine2/*含まず*/, int x12, int x22, int iLineMax/*含む*/) {
	const ST_RodSeg* pRodSeg = CONTAINING_RECORD(pRodSegBase, ST_RodSeg, base);
	int iRoad1 = (pRodSeg->road * 4) + 0;
	int iRoad2 = (pRodSeg->road * 4) + 3;
	RodGen_DrawSeg(&stRodGen, iLine1, iRoad1, x11, x21,
	                          iLine2, iRoad2, x12, x22, 0, iLineMax);
}
static int getObjDir(int dx, int dz, int vx) {
	int dir = (dx < 0) ? 8/*調整可*/ : -8/*調整可*/;	//自車から見て左側に見える(dx<0)敵車の方が、右に傾いて見える(dir>0)事に注意して下さい。
	dx >>= 2/*調整可*/;	//┬下記①②が逆方向に働いた場合に上手く相殺するよう目調整で調整して下さい。
	vx >>= 4/*調整可*/;	//┘
	//①自車から見た敵車の相対位置による傾きの計算
	dx = abs(dx);
	if(dx > dz) { dx = dz; }	//傾き45度以上は同じスプライトパターンを使うものと想定した。
	dir = dir * dx / dz;		//傾き0～45度を、0～8に変換する。
	//②敵車が属するセグメントによる傾きの計算
	dir += vx;
	dir = clamp(dir, -8/*調整可*/, 8/*調整可*/);
	return dir;
}
//□テストルーチンのP/ECE依存部分
#ifdef  PIECE
//{{2017/06/21変更:fnDrawObj関数の引数に、pRodSegBaseを追加しました。
//void fnDrawObj(const struct _ST_RodStr* pRodStr, const ST_RodObjBase* pRodObjBase, int x, int y, int dx, int vx, int iLineMax/*含む*/) {
//↓2017/06/21変更:fnDrawObj関数の引数に、pRodSegBaseを追加しました。
void fnDrawObj(const struct _ST_RodStr* pRodStr, const ST_RodSegBase* pRodSegBase, const ST_RodObjBase* pRodObjBase, int x, int y, int dx, int vx, int iLineMax/*含む*/) {
//}}2017/06/21変更:fnDrawObj関数の引数に、pRodSegBaseを追加しました。
	int save_bottom = render.bottom;
	render.bottom = iLineMax + 1;
	{
		const ST_RodObj* pRodObj = CONTAINING_RECORD(pRodObjBase, ST_RodObj, base);
		int dir, w, h;
		h = (VRAM_H / 2/*調整可*/) * pRodStr->scrZ / pRodObjBase->z;
		dir = getObjDir(dx, pRodObjBase->z, vx);			//左傾き:-8～+8:右傾き
		w = h * (abs(dir) + 4/*調整可*/) / (8/*調整可*/ + 4/*調整可*/);	//当サンプルでは簡易的にY軸回転を横サイズで表現しましたが、実際にはdirによってスプライトパターンを選択して下さい。
		render_rectangle_fill(&render, x - (w >> 1), y - h, w, h, pRodObj->color);
	}
	render.bottom = save_bottom;
}
void fnDrawBgd(const struct _ST_RodStr* pRodStr, int iLineMax/*含む*/) {
	render_rectangle_fill(&render, 0, 0, VRAM_W, iLineMax + 1, 1);
}
int app_main(int argc, char* argv[]) {
	test();
	return EXIT_SUCCESS;
}
#endif//PIECE
//□テストルーチンのWindows依存部分
#ifdef  WIN32
void update() {
	int SiHandle, GrHandle, i, x, y;
	SiHandle = MakePAL8ColorSoftImage(VRAM_W, VRAM_H);
	for(i = 0; i < 256; i++) {
		if(i < 4) { SetPaletteSoftImage(SiHandle, i, 255 - (i * 85), 255 - (i * 85), 255 - (i * 85), 0); }
		else      { SetPaletteSoftImage(SiHandle, i,              0,            255,              0, 0); }
	}
	for(y = 0; y < VRAM_H; y++) {
		for(x = 0; x < VRAM_W; x++) {
			DrawPixelPalCodeSoftImage(SiHandle, x, y, vbuff[(y * VRAM_W) + x]);
		}
	}
	GrHandle = CreateGraphFromSoftImage(SiHandle);
	DrawExtendGraph(4, 4, DISP_X - 4, DISP_Y - 4, GrHandle, FALSE);
	DeleteGraph(GrHandle);
	DeleteSoftImage(SiHandle);
	memset(vbuff, -1, sizeof vbuff);	//次回のために
}
void pset(int x, int y, int c) {
	if(((unsigned)x < VRAM_W) && ((unsigned)y < VRAM_H)) { vbuff[(y * VRAM_W) + x] = c; }
}
void boxf(int x, int y, int w, int h, int c) {
	int i, j;
	if(x < 0) { w += x; x = 0; }
	if(y < 0) { h += y; y = 0; }
	if(x + w >= VRAM_W) { w = VRAM_W - x; }
	if(y + h >= VRAM_H) { h = VRAM_H - y; }
	for(i = 0; i < w; i++) {
		for(j = 0; j < h; j++) {
			pset(x + i, y + j, c);
		}
	}
}
//{{2017/06/21変更:fnDrawObj関数の引数に、pRodSegBaseを追加しました。
//void fnDrawObj(const struct _ST_RodStr* pRodStr, const ST_RodObjBase* pRodObjBase, int x, int y, int dx, int vx, int iLineMax/*含む*/) {
//↓2017/06/21変更:fnDrawObj関数の引数に、pRodSegBaseを追加しました。
void fnDrawObj(const struct _ST_RodStr* pRodStr, const ST_RodSegBase* pRodSegBase, const ST_RodObjBase* pRodObjBase, int x, int y, int dx, int vx, int iLineMax/*含む*/) {
//}}2017/06/21変更:fnDrawObj関数の引数に、pRodSegBaseを追加しました。
	const ST_RodObj* pRodObj = CONTAINING_RECORD(pRodObjBase, ST_RodObj, base);
	int dir, w, h;
	h = (VRAM_H / 2/*調整可*/) * pRodStr->scrZ / pRodObjBase->z;
	dir = getObjDir(dx, pRodObjBase->z, vx);			//左傾き:-8～+8:右傾き
	w = h * (abs(dir) + 4/*調整可*/) / (8/*調整可*/ + 4/*調整可*/);	//当サンプルでは簡易的にY軸回転を横サイズで表現しましたが、実際にはdirによってスプライトパターンを選択して下さい。
	boxf(x - (w >> 1), y - h, w, h, pRodObj->color);
}
void fnDrawBgd(const struct _ST_RodStr* pRodStr, int iLineMax/*含む*/) {
	boxf(0, 0, VRAM_W, iLineMax + 1, 1);
}
int cpp_main(int argc, char* argv[]) {
	if(RRS_i_job(pRRS) != (RRS_n_job(pRRS) - 1)) { DIE(); }	//最後のジョブがUIジョブである事を確認する。
	if(!setjmp(env_exit)) { test(); }
	return EXIT_SUCCESS;
}
#endif//WIN32
#endif
