
import "sample_app.rs" ;


class	ShaderSprite	extends RenderableSprite
{
	protected SampleApp		m_app ;
	protected CustomShader	m_shader ;
	protected Vector3D 		m_vCameraPos ;		// 視点
	protected Vector3D 		m_vCameraTarget ;	// 注視点
	protected Vector3D 		m_vCameraTop ;		// 天頂方向
	protected Matrix3D		m_matTerrain ;
	protected Vector3D		m_vTerrain ;
	protected Vector3D		m_vLightDir ;
	protected Image			m_imgTerrain ;

	protected Matrix3D		m_matPlayer ;		// プレイヤーの向き
	protected Vector3D		m_vPlayerPos ;		// プレイヤーの位置
	protected Vector3D		m_vPlayerSpeed ;	// プレイヤーの速度
	protected double		m_fpPlayerSpeed ;	// プレイヤーの速度（疑似加速度）
	protected double		m_rpsPlayerRotX ;	// プレイヤーの回転速度 [rad/sec]
	protected double		m_rpsPlayerRotY ;
	protected double		m_rpsPlayerRotZ ;

	public static const double	SPEED_BASE	= 3.0 ;
	public static const double	SPEED_MAX	= 15.0 ;
	public static const double	SPEED_MIN	= 1.0 ;
	public static const double	SPEED_AIR_R	= 0.1 ;


	public ShaderSprite( SampleApp app )
	{
		super() ;
		m_app = app ;
	}

	// 表示領域矩形
	protected boolean getRenderRect( Rect rect )
	{
		rect.x = 0 ;
		rect.y = 0 ;
		rect.w = SampleApp.SCREEN_WIDTH ;
		rect.h = SampleApp.SCREEN_HEIGHT ;
		return	true ;
	}

	// マウスクリック当たり判定
	protected boolean isHitTest( double xLocal, double yLocal )
	{
		return	false ;
	}

	// 描画
	protected void onRender( RenderContext render, int s3dView )
	{
		// 3D レンダリングのための設定
		setPerspective( render ) ;
		setCameraTo( render ) ;

		// 3D 描画開始
		render.begin3DRenderer() ;
		render.pushTransformation() ;
		render.attachCustomShader( m_shader ) ;

		// シェーダーパラメータ設定
		Matrix3D	matITerrain = m_matTerrain.clone().inverse() ;
		Vector3D	vITerrain = matITerrain.mul( m_vTerrain.clone().mul(-1) ) ;

		render.setCustomShaderUniform
			( "u_mat3Terrain",
				CustomShader.uniformMatrix3x3,
				m_matTerrain, 1 ) ;
		render.setCustomShaderUniform
			( "u_mat3ITerrain",
				CustomShader.uniformMatrix3x3,
				matITerrain, 1 ) ;
		render.setCustomShaderUniform
			( "u_vITerrain",
				CustomShader.uniformVector3D,
				vITerrain, 1 ) ;
		render.setCustomShaderUniform
			( "u_vLightDir",
				CustomShader.uniformVector3D,
				m_vLightDir, 1 ) ;

		Int64Pointer	pTextures = new Int64Pointer( 1 ) ;
		pTextures[0] = m_imgTerrain.getNativePointer() ;
		render.setCustomShaderUniform
			( "u_texTerrain",
				CustomShader.uniformTexture,
				pTextures, 1 ) ;

		// 描画
		render.fillRectangle
			( 0, 0, SampleApp.SCREEN_WIDTH,
					SampleApp.SCREEN_HEIGHT, 0xffffffff ) ;

		// 3D 描画の終了とシェーディング設定を元に戻す
		render.popTransformation() ;
		render.end3DRenderer() ;
		render.finish() ;
	}

	// タイマー処理
	protected void onTimer( int msecPast )
	{
		const double	secPast = msecPast * 0.001 ;

		// ジョイスティックの状態を取得する
		VirtualInput	input = m_app.getInput() ;
		Vector3D4		vJoy = new Vector3D4( 0, 0, 0, 0 ) ;

		input.getAnalogJoyPosition( vJoy, VirtualInput.joyStickXInput1 ) ;

		if ( input.isJoyButtonPushing( VirtualInput.joyLeft ) )
		{
			vJoy.x = Math.max( vJoy.x - 1.0, -1.0 ) ;
		}
		if ( input.isJoyButtonPushing( VirtualInput.joyRight ) )
		{
			vJoy.x = Math.min( vJoy.x + 1.0, 1.0 ) ;
		}
		if ( input.isJoyButtonPushing( VirtualInput.joyUp ) )
		{
			vJoy.y = Math.max( vJoy.y - 1.0, -1.0 ) ;
		}
		if ( input.isJoyButtonPushing( VirtualInput.joyDown ) )
		{
			vJoy.y = Math.min( vJoy.y + 1.0, 1.0 ) ;
		}
		if ( input.isJoyButtonPushing( VirtualInput.joyButton1 )
			|| input.isJoyButtonPushing
				( VirtualInput.xinputButtonA, VirtualInput.joyStickXInput1 ) )
		{
			// JoyStick 1 ボタン、又は XInput A ボタンで加速
			m_fpPlayerSpeed =
				Math.min( m_fpPlayerSpeed
							+ secPast * 10.0, SPEED_MAX ) ;
		}
		else if ( input.isJoyButtonPushing( VirtualInput.joyButton2 )
			|| input.isJoyButtonPushing
				( VirtualInput.xinputButtonB, VirtualInput.joyStickXInput1 ) )
		{
			// JoyStick 2 ボタン、又は XInput B ボタンで原則
			m_fpPlayerSpeed = SPEED_MIN ;
		}
		else
		{
			// 空気抵抗
			m_fpPlayerSpeed =
				SPEED_BASE +
					(m_fpPlayerSpeed - SPEED_BASE)
						* Math.pow( SPEED_AIR_R, secPast ) ;
		}

		// 左右スティックは z 軸回転、上下スティックは x 軸回転
		double		radX = vJoy.y / 2.0 * Math.PI ;	// 2秒で180度回る速度
		double		radY = vJoy.z / 3.0 * Math.PI ;	// 3秒で180度回る速度
		double		radZ = vJoy.x / 1.0 * Math.PI ;	// 1秒で180度回る速度

		// 回転速度ローパスフィルタ
		m_rpsPlayerRotX = m_rpsPlayerRotX * 0.9 + radX * 0.1 ;
		m_rpsPlayerRotY = m_rpsPlayerRotY * 0.9 + radY * 0.1 ;
		m_rpsPlayerRotZ = m_rpsPlayerRotZ * 0.9 + radZ * 0.1 ;

		// 回転
		radX = m_rpsPlayerRotX * secPast ;
		radY = m_rpsPlayerRotY * secPast ;
		radZ = m_rpsPlayerRotZ * secPast ;

		Vector3D	vX = m_matPlayer.mul( new Vector3D( 1, 0, 0 ) ) ;
		Vector3D	vY = m_matPlayer.mul( new Vector3D( 0, 1, 0 ) ) ;
		Vector3D	vZ = m_matPlayer.mul( new Vector3D( 0, 0, 1 ) ) ;

		Matrix3D	matX = (new Matrix3D()).rotateOnVectorOf
								( vX, Math.sin(radX), Math.cos(radX) ) ;
		Matrix3D	matY = (new Matrix3D()).rotateOnVectorOf
								( vY, Math.sin(radY), Math.cos(radY) ) ;
		Matrix3D	matZ = (new Matrix3D()).rotateOnVectorOf
								( vZ, Math.sin(radZ), Math.cos(radZ) ) ;

		m_matPlayer = matZ.mul(matX).mul(matY).mul(m_matPlayer) ;

		// 速度を加算する
		double	speed = m_fpPlayerSpeed * secPast ;
		m_vPlayerSpeed.add
			( m_matPlayer.mul( new Vector3D( 0, 0, speed ) ) ) ;

		// 空気抵抗
		speed = m_vPlayerSpeed.absolute() ;
		speed = m_fpPlayerSpeed
					+ (speed - m_fpPlayerSpeed)
							* Math.pow( SPEED_AIR_R, secPast ) ;
		m_vPlayerSpeed.normalize() ;
		m_vPlayerSpeed.mul( speed ) ;

		// プレイヤーの座標を更新する
		m_vPlayerPos.add( m_vPlayerSpeed.clone().mul( secPast ) ) ;

		// カメラを更新する
		// （m_matPlayer の逆行列と、逆行列と m_vPlayer の積を
		// 　直接 setCamera に渡してもよいが、ここでは座標を設定する）
		m_vCameraPos.copy( m_vPlayerPos ) ;
		m_vCameraTarget =
			m_matPlayer.mul( new Vector3D( 0, 0, 1 ) ).add( m_vCameraPos ) ;
		m_vCameraTop = m_matPlayer.mul( new Vector3D( 0, -1, 0 ) ) ;

		postUpdate() ;
	}


	// 初期設定
	public boolean initialize( void )
	{
		m_vCameraPos = new Vector3D( 0.0, 0.0, 0.0 ) ;
		m_vCameraTarget = new Vector3D( 0.0, 0.0, 1.0 ) ;
		m_vCameraTop = new Vector3D( 0.0, -1.0, 0.0 ) ;

		m_matTerrain = new Matrix3D( 20, 0, 0,  0, 7, 0,  0, 0, 20 ) ;
		m_vTerrain = new Vector3D( 0, 8, 0 ) ;
		m_vLightDir = new Vector3D( 1.0, 1.0, 1.0 ) ;

		m_matPlayer = new Matrix3D( 1, 0, 0,  0, 1, 0,  0, 0, 1 ) ;
		m_vPlayerPos = new Vector3D( 0.0, 0.0, 0.0 ) ;
		m_vPlayerSpeed = new Vector3D( 0.0, 0.0, SPEED_BASE ) ;
		m_fpPlayerSpeed = SPEED_BASE ;
		m_rpsPlayerRotX = 0.0 ;
		m_rpsPlayerRotY = 0.0 ;
		m_rpsPlayerRotZ = 0.0 ;

		m_imgTerrain = new Image() ;
		if ( !m_imgTerrain.loadImage( "terrain.png" ) )
		{
			outputError( "terrain.png の読み込みに失敗しました。" ) ;
		}
		m_imgTerrain.normalizeToTexture( Image.bufferSampleTiling ) ;

		prepareShader() ;
	}

	// シェーダー準備
	public void prepareShader( void )
	{
		RenderDevice.ShaderDesc	shddsc = new RenderDevice.ShaderDesc() ;
		if ( !shddsc.loadDescriptor( "terrain_march.shddsc" ) )
		{
			outputError( "terrain_march.shddsc の読み込みに失敗しました。" ) ;
		}
		else
		{
			RenderDevice	device = m_app.getWindow().getRenderDevice() ;
			try
			{
				m_shader = device.buildCustomShader( "terrain_march", shddsc ) ;
			}
			catch ( RenderDevice.BuildShaderException e )
			{
				outputError
					( "シェーダーのビルドに失敗しました。\n>>> " + e.toString() ) ;
			}
		}
	}

	// 透視変換設定
	public void setPerspective( RenderContext render )
	{
		const double	degFOV = 65 ;
		const double	radHalfFOV = degFOV * 0.5 * Math.PI / 180 ;
		//
		Vector3D	vScreen = new Vector3D() ;
		vScreen.x = SampleApp.SCREEN_WIDTH * 0.5 ;
		vScreen.y = SampleApp.SCREEN_HEIGHT * 0.5 ;
		vScreen.z = vScreen.x / Math.tan(radHalfFOV) ;
		//
		render.setProjectionScreen( vScreen ) ;
		render.setZClipRange( 0.1, 100.0 ) ;
	}

	// カメラ設定
	public void setCameraTo( RenderContext render )
	{
		Matrix3D	matCamera = new Matrix3D() ;
		Vector3D	vCamera =
			matCamera.cameraAngleOf
				( m_vCameraTarget, m_vCameraPos, m_vCameraTop ) ;
		render.setCamera( matCamera, vCamera ) ;
	}

	// エラーメッセージ出力
	public void outputError( String err )
	{
		System.console().printf( "error: %s\n", err ) ;
	}
}


class	InputJoystickRunnable	implements Runnable
{
	public SampleApp	m_app ;
	public ShaderSprite	m_sprite ;

	public InputJoystickRunnable( SampleApp app )
	{
		m_app = app ;
	}

	public void run()
	{
		// RenderableSprite 準備
		m_sprite = new ShaderSprite( m_app ) ;
		m_sprite.setErrorMode( RenderableSprite.errorStdOut ) ;
		m_sprite.initialize() ;

		// 表示
		m_app.getWindow().addChild( m_sprite ) ;
		m_app.waitFor( function() : boolean { return false; } ) ;
	}

	public void finalize( void )
	{
		releaseRef() ;
	}

	public void releaseRef()
	{
		if ( m_app != null )
		{
			m_app.getWindow().detachChild( m_sprite ) ;
			m_sprite.dispose() ;
			m_sprite = null ;
			m_app = null ;
		}
	}
}


int main( String[] args )
{
	SampleApp	app = new SampleApp() ;
	app.initialize( "Input Joystick Sample" ) ;

//	InputJoystickRunnable	runnable = new InputJoystickRunnable(app) ;
	try
	{
		app.run( new InputJoystickRunnable(app) ) ;
	}
	finally
	{
//		runnable.releaseRef() ;
		app.release() ;
	}
	return	0 ;
}


