デスクトップマスコットを作ってみた

息抜きと趣味のプログラミングも兼ねて作成してみました。
作ってみたと書いてはいますが下記の記事の発展版みたいな感じです。
[デスクトップマスコットの作り方](https://qiita.com/massoumen/items/2985a0fb30472b97a590)
上記の記事の内容までできていることが前提となります。


# デスクトップ内を移動させる
表示したモデルをデスクトップ内をマウスで自由に移動させたいですよね。
移動させようと思うと少し面倒かもしれません。
というのもマウスの座標はスクリーン座標、モデルはワールド座標を使用しており、これらの座標を合わせてあげる必要があります。
ワールド座標、スクリーン座標について記述するとそれだけで長くなるのと私も完璧に理解しているわけではないので私が勉強しているリンク先を載せておきます
[座標変換](https://yttm-work.jp/gmpg/gmpg_0002.html)
[モデルの描画について](https://www.slideshare.net/AimingStudy/20130708-aimosaka-instudyyasui)

・マウスポインタがモデル上にあるかの判定
まずは左クリックした際にマウスポインタがモデル上にあるかの判定です。
考え方としては**マウスを左クリックした際に線分を飛ばし、それがモデルに当たればマウスポインタはモデル上にある**と判定します。
その際に線分の始点、終点はワールド座標へ変換します。
コードにすると下記のような感じです。

```C#:
int MouseX, MouseY ;

// マウスカーソルの座標を取得
GetMousePoint( &MouseX, &MouseY ) ;
// マウスボタンの入力状態を更新
PrevInput = NowInput ;
NowInput = GetMouseInput() ;
EdgeInput = NowInput & ~PrevInput ;

// 既にモデルを掴んでいるかどうかで処理を分岐
if( Catch == 0 )
{
　　　　// 掴んでいない場合

	// 左クリックされたらモデルをクリックしたかを調べる
	if( EdgeInput & MOUSE_INPUT_1 )
	{
		VECTOR ScreenPos1 ;
		VECTOR ScreenPos2 ;
		VECTOR WorldPos1 ;
		VECTOR WorldPos2 ;

		// モデルとの当たり判定用の線分の２座標を作成
		ScreenPos1.x = ( float )MouseX ;
		ScreenPos1.y = ( float )MouseY ;
		ScreenPos1.z = 0.0f ;

		ScreenPos2.x = ( float )MouseX ;
		ScreenPos2.y = ( float )MouseY ;
		ScreenPos2.z = 1.0f ;

		WorldPos1 = ConvScreenPosToWorldPos( ScreenPos1 ) ;
		WorldPos2 = ConvScreenPosToWorldPos( ScreenPos2 ) ;

		// モデルの当たり判定情報を更新
		MV1RefreshCollInfo( ModelHandle, -1 ) ;

		// モデルと線分の当たり判定
		MV1_COLL_RESULT_POLY Result = MV1CollCheck_Line( ModelHandle, -1, WorldPos1, WorldPos2 ) ;
// 当たっていたら掴み状態にする
		if( Result.HitFlag )
		{
			// 掴んでいるかどうかのフラグを立てる
			Catch = 1 ;

			// 掴んだときのスクリーン座標を保存
			CatchMouseX = MouseX ;
			CatchMouseY = MouseY ;

			// 掴んだときのモデルのワールド座標を保存
			Catch3DModelPosition = MV1GetPosition( ModelHandle ) ;

			// 掴んだときのモデルと線分が当たった座標を保存( 座標をスクリーン座標に変換したものも保存しておく )
			Catch3DHitPosition = Result.HitPosition ;
			Catch2DHitPosition = ConvWorldPosToScreenPos( Catch3DHitPosition ) ;
		}
	}
}
```

抜粋しているので変なところもありますがこんな感じです。
EdgeInputは前回のフレームでは押されていなくて今回のフレームでは押されているボタンのみの押下状態を格納しております。
何もないところをクリックしてからオブジェクト上にマウスを動かした際にクリックしたと判定されるとまずいからです。

線分の始点、終点の座標をそれぞれConvScreenPosToWorldPos関数でワールド座標に変換しています。
MV1CollCheck_Lineで線分とモデルの当たり判定をしています。

・モデルの移動
次にマウスの移動に合わせてモデルを移動させる処理です。
処理としては
モデルをクリックしたときの座標を保存しておき、左クリックが離されるまでその座標との差分で計算する。
モデルの移動値にはマウス座標の移動分をそのままモデルの移動分とするのではなく、マウスがクリックした３Ｄ座標にマウス座標の移動分を加味した３Ｄ座標の差分をモデルの移動値とする。
*マウスでクリックしたスクリーン座標とモデルの３Ｄ座標をスクリーン座標化したものが離れていればいるほどマウスカーソルを移動した際の誤差が大きくなるため

コードは下記のようになります

```
// 掴んでいる場合

// マウスの左クリックが離されていたら掴み状態を解除
if( ( NowInput & MOUSE_INPUT_1 ) == 0 )
{
    Catch = 0 ;
}
else
{
	// 掴み状態が継続していたらマウスカーソルの移動に合わせてモデルも移動

	float MoveX, MoveY ;
	VECTOR NowCatch2DHitPosition ;
	VECTOR NowCatch3DHitPosition ;
	VECTOR Now3DModelPosition ;

	// 掴んだときのマウス座標から現在のマウス座標までの移動分を算出
	MoveX = ( float )( MouseX - CatchMouseX ) ;
	MoveY = ( float )( MouseY - CatchMouseY ) ;

	// 掴んだときのモデルと線分が当たった座標をスクリーン座標に変換したものにマウスの移動分を足す
	NowCatch2DHitPosition.x = Catch2DHitPosition.x + MoveX ;
	NowCatch2DHitPosition.y = Catch2DHitPosition.y + MoveY ;
	NowCatch2DHitPosition.z = Catch2DHitPosition.z ;

	// 掴んだときのモデルと線分が当たった座標をスクリーン座標に変換したものにマウスの移動分を足した座標をワールド座標に変換
	NowCatch3DHitPosition = ConvScreenPosToWorldPos( NowCatch2DHitPosition ) ;

	// 掴んだときのモデルのワールド座標に『掴んだときのモデルと線分が当たった座標にマウスの移動分を足した座標をワールド座標に
	// 変換した座標』と、『掴んだときのモデルと線分が当たった座標』との差分を加算
	Now3DModelPosition.x = Catch3DModelPosition.x + NowCatch3DHitPosition.x - Catch3DHitPosition.x ;
	Now3DModelPosition.y = Catch3DModelPosition.y + NowCatch3DHitPosition.y - Catch3DHitPosition.y ;
	Now3DModelPosition.z = Catch3DModelPosition.z + NowCatch3DHitPosition.z - Catch3DHitPosition.z ;

	// ↑の計算で求まった新しい座標をモデルの座標としてセット
	MV1SetPosition( ModelHandle, Now3DModelPosition ) ;
}
```
一度スクリーン座標でマウスの移動値を計算しておき、その移動値をモデルと線分が当たった時のスクリーン座標に足し、それをワールド座標に変換します。
そしてクリック時のモデルのワールド座標にマウスがクリックした３Ｄ座標にマウス座標の移動分を加味した３Ｄ座標の差分をモデルの移動値を加算しています。
移動値には掴んだときのモデルと線分が当たった座標分も含まれてしまっているのでこちらを引いておきます。引かないとクリック時にモデルの原点がクリックした座標に飛んで行ってしまうと思います。

全体のコードです

```
int MouseX, MouseY ;

// マウスカーソルの座標を取得
GetMousePoint( &MouseX, &MouseY ) ;

// マウスボタンの入力状態を更新
PrevInput = NowInput ;
NowInput = GetMouseInput() ;
EdgeInput = NowInput & ~PrevInput ;

// 既にモデルを掴んでいるかどうかで処理を分岐
if( Catch == 0 )
{
        // 掴んでいない場合

	// 左クリックされたらモデルをクリックしたかを調べる
	if( EdgeInput & MOUSE_INPUT_1 )
	{
		VECTOR ScreenPos1 ;
		VECTOR ScreenPos2 ;
		VECTOR WorldPos1 ;
		VECTOR WorldPos2 ;

		// モデルとの当たり判定用の線分の２座標を作成
		ScreenPos1.x = ( float )MouseX ;
		ScreenPos1.y = ( float )MouseY ;
		ScreenPos1.z = 0.0f ;

		ScreenPos2.x = ( float )MouseX ;
		ScreenPos2.y = ( float )MouseY ;
		ScreenPos2.z = 1.0f ;

	        WorldPos1 = ConvScreenPosToWorldPos( ScreenPos1 ) ;
		WorldPos2 = ConvScreenPosToWorldPos( ScreenPos2 ) ;

		// モデルの当たり判定情報を更新
		MV1RefreshCollInfo( ModelHandle, -1 ) ;

		// モデルと線分の当たり判定
		MV1_COLL_RESULT_POLY Result = MV1CollCheck_Line( ModelHandle, -1, WorldPos1, WorldPos2 ) ;

		// 当たっていたら掴み状態にする
		if( Result.HitFlag )
		{
			// 掴んでいるかどうかのフラグを立てる
			Catch = 1 ;

			// 掴んだときのスクリーン座標を保存
			CatchMouseX = MouseX ;
			CatchMouseY = MouseY ;

			// 掴んだときのモデルのワールド座標を保存
			Catch3DModelPosition = MV1GetPosition( ModelHandle ) ;

			// 掴んだときのモデルと線分が当たった座標を保存( 座標をスクリーン座標に変換したものも保存しておく )
			Catch3DHitPosition = Result.HitPosition ;
			Catch2DHitPosition = ConvWorldPosToScreenPos( Catch3DHitPosition ) ;
		}
	}
}
else
{
	// 掴んでいる場合

	// マウスの左クリックが離されていたら掴み状態を解除
	if( ( NowInput & MOUSE_INPUT_1 ) == 0 )
	{
		Catch = 0 ;
	}
	else
	{
		// 掴み状態が継続していたらマウスカーソルの移動に合わせてモデルも移動

		float MoveX, MoveY ;
		VECTOR NowCatch2DHitPosition ;
		VECTOR NowCatch3DHitPosition ;
		VECTOR Now3DModelPosition ;

		// 掴んだときのマウス座標から現在のマウス座標までの移動分を算出
		MoveX = ( float )( MouseX - CatchMouseX ) ;
		MoveY = ( float )( MouseY - CatchMouseY ) ;

		// 掴んだときのモデルと線分が当たった座標をスクリーン座標に変換したものにマウスの移動分を足す
		NowCatch2DHitPosition.x = Catch2DHitPosition.x + MoveX ;
		NowCatch2DHitPosition.y = Catch2DHitPosition.y + MoveY ;
		NowCatch2DHitPosition.z = Catch2DHitPosition.z ;

		// 掴んだときのモデルと線分が当たった座標をスクリーン座標に変換したものにマウスの移動分を足した座標をワールド座標に変換
		NowCatch3DHitPosition = ConvScreenPosToWorldPos( NowCatch2DHitPosition ) ;

		// 掴んだときのモデルのワールド座標に『掴んだときのモデルと線分が当たった座標にマウスの移動分を足した座標をワールド座標に
		// 変換した座標』と、『掴んだときのモデルと線分が当たった座標』との差分を加算
		Now3DModelPosition.x = Catch3DModelPosition.x + NowCatch3DHitPosition.x - Catch3DHitPosition.x ;
		Now3DModelPosition.y = Catch3DModelPosition.y + NowCatch3DHitPosition.y - Catch3DHitPosition.y ;
		Now3DModelPosition.z = Catch3DModelPosition.z + NowCatch3DHitPosition.z - Catch3DHitPosition.z ;

		// ↑の計算で求まった新しい座標をモデルの座標としてセット
		MV1SetPosition( ModelHandle, Now3DModelPosition ) ;
	}
```

分かりにくいところもあると思いますが参考になったら幸いです。
変数の定義とかの部分は省略しています。
私はこれを関数化して使用しています。
個人的には3Dの当たり判定、座標変換の勉強にはなったと思いました。

次回はホイールの動きに合わせてのモデルの拡大縮小を書こうと思います。