【C++】めちゃくちゃ強引にポリゴンを変動させる #2【3D】

はじめに
---
この記事は

https://qiita.com/0reo07/items/f2424069412b97a02848

の後半部分です。

この記事では、プレイヤーが落下後、衝突した足元のポリゴンを中心にある程度の範囲をｙ軸方向に減少させて凹む（沈む）ようにしたいと思います。

引き続き、言語は **C++** 、 **エンジンを使わず** にVisualStudioで、ライブラリは **DXライブラリ** で制作を進めていきます。

::: note warn
【記事中のコードについての注意事項】
分かりやすさのため実際のものから書き換えている部分や省略している部分があります。
:::

目次
---
1. 衝突判定
1. AABBによる衝突判定
1. ポリゴン変動
1. 参考

# 衝突判定

まずはプレイヤーとポリゴンの衝突判定をします。
衝突判定には[HitCheck_Line_Triangle](https://dxlib.xsrv.jp/function/dxfunc_3d_math.html#R16N4)を使います。

```c++:Player.h
private:
    //~省略~
    //ポリゴンとの衝突判定
    void CollisionTriangle(void);

    //ジャンプ量
    VECTOR jumpPow_;
    //移動後座標
    VECTOR movedPos_;
    //線分の始点
    VECTOR gravityHitUp_;
    //線分の終点
    VECTOR gravityHitUp_;
};
```
```c++:Player.cpp
void Player::CollisionTriangle(void)
{
    //~省略~
	//重力の加算の計算
	//ジャンプ量を加算
	movedPos_ = VAdd(movedPos_, jumpPow_);
	//重力方向
	VECTOR dirGravity = {0.0f,-1.0f,0.0f};
	//重力の反対方向
	VECTOR dirUpGravity = {-0.0f,1.0f,-0.0f};
	
    //重力の強さ
	float gravityPow = 10.0f;
	//線分
	float checkPow = 50.0f;

    //線分の始点と終点の更新
	//線分の始点
	gravityHitUp_ = VAdd(movedPos_, VScale(dirUpGravity, gravityPow));
	gravityHitUp_ = VAdd(gravityHitUp_, VScale(dirUpGravity, checkPow * 3.0f));
	//線分の終点
	gravityHitDown_ = VAdd(movedPos_, dirGravity);

    //ステージ情報の取得
    //ポリゴン
    auto poly = stage_->GetPolygon(); //MV1_REF_POLYGONLIST polygonList_を取得
    //頂点
	auto vertex = stage_->GetVertex();//std::vector<VERTEX3D> vertexes_を取得
    
	//線分とステージモデルの衝突判定
	for (int p = 0; p < poly.VertexNum; p++)
	{
		//衝突判定
		auto hit = HitCheck_Line_Triangle(gravityHitUp_, gravityHitDown_,
			vertex[p].pos, vertex[p+1].pos, vertex[p+2].pos);

        //衝突しているとき、かつ落下しているとき
		if (hit.HitFlag > 0 && VDot(dirGravity, jumpPow_) > 0)
		{
			//~省略~
		}
	}
}
```
出力はこうなります。
![compare1.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3402790/231f841e-b820-7db1-f51e-df0304be6fbc.gif)
この処理以前の出力（左）と比べると処理が遅くなっているのがわかります。

全てのポリゴンとの衝突判定を毎フレーム計算しているのが原因のようです。
処理速度を速くするため、 **AABB** による衝突処理を挟もうと思います。

# AABBによる衝突判定

**AABB**(axis-aligned bounding box) とは、「立方体による簡易衝突判定」のことです。

:::note info
色々な処理を毎フレームするより、一度二つの立方体として考えて、立方体同士の衝突判定をした方が処理が速いよね。みたいなこと。
:::

立方体は二つの頂点座標があれば生成できるので、ポリゴンの頂点の一番高い座標を"Max"、一番低い座標を"Min"として、プレイヤーとポリゴンそれぞれで立方体を生成していきます。
![立方体のイメージ図](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3402790/906690d3-69ec-ffd8-e7c1-bf06309b4104.png)

>詳しい説明は参考にした[こちらのサイト](http://marupeke296.com/COL_3D_No11_AABBvsPoint.html)をご覧ください。

AABBの衝突判定後、衝突している立方体のポリゴンだけで詳しく衝突判定をするようにします。
今回はAABBの構造体で情報を管理します。
```c++
//AABBの構造体
struct AABB{
    VECTOR max;
    VECTOR min;
};

//AABB情報の作成
AABB GetAABBTriangle(const VECTOR& tPos1, const VECTOR& tPos2, const VECTOR& tPos3){
    AABB ret = AABB();

    ret.min.x = tPos1.x;
    ret.max.x = tPos1.x;
    ret.min.y = tPos1.y;
    ret.max.y = tPos1.y;
    ret.min.z = tPos1.z;
    ret.max.z = tPos1.z;

    if (ret.min.x > tPos2.x) { ret.min.x = tPos2.x; }
    else if (ret.max.x < tPos2.x) { ret.max.x = tPos2.x; }
    if (ret.min.x > tPos3.x) { ret.min.x = tPos3.x; }
    else if (ret.max.x < tPos3.x) { ret.max.x = tPos3.x; }

    if (ret.min.y > tPos2.y) { ret.min.y = tPos2.y; }
    else if (ret.max.y < tPos2.y) { ret.max.y = tPos2.y; }
    if (ret.min.y > tPos3.y) { ret.min.y = tPos3.y; }
    else if (ret.max.y < tPos3.y) { ret.max.y = tPos3.y; }

    if (ret.min.z > tPos2.z) { ret.min.z = tPos2.z; }
    else if (ret.max.z < tPos2.z) { ret.max.z = tPos2.z; }
    if (ret.min.z > tPos3.z) { ret.min.z = tPos3.z; }
    else if (ret.max.z < tPos3.z) { ret.max.z = tPos3.z; }

    return ret;
}
```
### ステージクラスでのAABB立方体
この構造体と関数を使って、まずはステージクラスでAABBの立方体を構成する情報を変数へ格納します。
```c++:Stage.h

public:
    struct POLYGON{
        //ポリゴンの頂点情報
        VERTEX3D vertexes[3];   //※既に存在している情報なので本来は参照にしてメモリの使用量を削減させた方が良い
        
        //AABBの情報も管理
        AABB aabb;
    };
	struct POLYGON
	{
        //ポリゴンの頂点情報
		VERTEX3D vertexs[3];  //※既に存在している情報なので本来は参照にしてメモリの使用量を削減させた方が良い
       
       //vertexs_(描画用)のポリゴンと要素番号を合わせる
		int idx[3];

        //AABBの情報も管理
		AABB aabb;
	};

    //~省略~
private:
    //AABB用ポリゴン生成
    void CreatePolygon(void);

    //AABBのポリゴン情報格納先
    std::vector<POLYGON> polygons_;
};
```
```c++:Stage.cpp
void Stage::CreatePolygon(void)
{
	// 参照用メッシュ情報の取得
	polygonList_ = MV1GetReferenceMesh(transform_.modelId, -1, TRUE);

	int num = polygonList_.PolygonNum;
	for (int i = 0; i < num; i++)
	{
		auto poly = polygonList_.Polygons[i];

		POLYGON polygon;

		for (int p = 0; p < 3; p++)
		{
			//頂点の要素番号を取得
			int vIndex = poly.VIndex[p];

			//取得した頂点の要素番号を使って、頂点情報を取得
			auto verRef = polygonList_.Vertexs[vIndex];

			//頂点情報へ変換
			VERTEX3D v;
			v.pos = verRef.Position;
			v.dif = verRef.DiffuseColor;
			v.spc = verRef.SpecularColor;
			v.norm = verRef.Normal;
			v.u = verRef.TexCoord[0].u;
			v.v = verRef.TexCoord[0].v;

			polygon.vertexs[p] = v;

			//動的配列に格納（描画用）
			vertexes_.emplace_back(v);
		}
        
        //AABB情報の作成
		polygon.aabb = GetAABBTriangle(
			polygon.vertexs[0].pos,
			polygon.vertexs[1].pos,
			polygon.vertexs[2].pos
		);

        //衝突判定用ポリゴンと描画用ポリゴンの要素番号を合わせる
		polygon.idx[0] = vertexes_.size() - 3;
		polygon.idx[1] = vertexes_.size() - 2;
		polygon.idx[2] = vertexes_.size() - 1;

		//動的配列に格納
		polygons_.emplace_back(polygon);
	}
}
```
::: note warn
<details><summary>※描画について</summary>

```c++:Stage.cpp
void Stage::Draw(void)
{

	//ポリゴン描画
	//DrawPolygon3D(polygons_.data()->vertexs, polygonList_.PolygonNum, DX_NONE_GRAPH, FALSE);
	
	// 素直に描画(但し遅い)
	//for (auto& p : polygons_)
	//{
	//	DrawPolygon3D(p.vertexs, 1, DX_NONE_GRAPH, FALSE);
	//}

	// 頂点情報を作って描画(まだ遅い)
	//std::vector<VERTEX3D> vs;
	//for (auto& p : polygons_)
	//{
	//	vs.emplace_back(p.vertexs[0]);
	//	vs.emplace_back(p.vertexs[1]);
	//	vs.emplace_back(p.vertexs[2]);
	//}
	//DrawPolygon3D(vs.data(), polygons_.size(), DX_NONE_GRAPH, FALSE);

	// 最速
	DrawPolygon3D(vertexes_.data(), polygonList_.PolygonNum, DX_NONE_GRAPH, FALSE);

	//デバッグ描画
	//DrawDebug();
}
```

</details>

注意点はステージの描画です。新しくAABBの情報を加えたポリゴン情報で描画すると、ここでも処理が遅くなってしまうので記事前半と同じように描画しています。
そのため既に存在している頂点情報が二重でメモリに存在しているので、`struct POLYGON`の頂点情報は参照にすべきですが、今回は省きます。
:::
ポリゴンひとつひとつにAABB立方体が生成できました。

### プレイヤークラスのAABB立方体
プレイヤークラスでも行います。
立方体の大きさはプレイヤーを囲うように値を調整します。
```c++:Player.h
private:
    //~省略~ 
	//プレイヤーのAABB衝突判定
	AABB playerAABB_;
};
```
```c++:Player.cpp
void Player::CollisionTriangle(void)
{
    //~省略~

	//プレイヤーの衝突判定立方体
	auto playerPos_ = VAdd(movedPos_, VScale(dirUpGravity, gravityPow));
	playerAABB_.min = VAdd(playerPos_, VECTOR{ -50,0,-15 });
	playerAABB_.max = VAdd(playerPos_, VECTOR{ 50,200,15 });
	auto playerOldPos = playerPos_;
}
```
これでプレイヤーとステージそれぞれにAABBの立方体ができたので、この立方体を使ってAABBの衝突判定をします。

### 衝突判定
ポリゴンの衝突判定で作成したCollisionTriangleクラスを書き換えます。

```c++:Player.cpp
void Player::CollisionTriangle(void)
{
    //~省略~
    //ステージのポリゴン情報を取得
	auto& polygons = stage_->GetPolygons(); //std::vector<POLYGON> polygons_を取得

	for (auto& p : polygons)
	{
		//AABB衝突判定
		if (!(playerAABB_.min.x > p.aabb.max.x ||
			playerAABB_.min.y > p.aabb.max.y ||
			playerAABB_.min.z > p.aabb.max.z ||
			playerAABB_.max.x < p.aabb.min.x ||
			playerAABB_.max.y < p.aabb.min.y ||
			playerAABB_.max.z < p.aabb.min.z))
		{
            //さらに細かい衝突判定
			// 三角形（ポリゴン）と線分（プレイヤー）の衝突判定
            auto hit = HitCheck_Line_Triangle(gravityHitUp_, gravityHitDown_,
			p.vertexes[0].pos, p.vertexes[1].pos, p.vertexes[2].pos);

			if(hit.HitFlag > 0 && VDot(dirGravity, jumpPow_) > 0)
			{
				//~省略~
			}
		}
	}
}
```
結果はこうなります。
![compare2.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3402790/49675484-f118-ab93-0619-116d82d7228e.gif)

AABBでの衝突によってAABB衝突処理以前（左）と比べて処理が速くなっています。

# ポリゴンの変動

最後にポリゴンの変動をしていきます。
プレイヤークラスから値を渡してステージクラスでポリゴンを変動させます。

### 関数の作成
ステージクラスで衝突したポリゴンを変動させる関数を書きます。

```c++:Stage.h
public:
    //~省略~
    //球体同士の衝突判定
    bool IsHitSphere(const VECTOR& pos1, float radius1, const VECTOR& pos2);

	//衝突情報格納
	void SetHitPolygon(VECTOR hitPos);
};
```
```c++:Stage.cpp
bool Stage::IsHitSphere(const VECTOR& pos1, float radius1, const VECTOR& pos2)
{
    // 球体同士の衝突判定
    bool ret = false;

    // お互いの半径の合計
    float radius = radius1;

    // 座標の差からお互いの距離を取る
    VECTOR diff = VSub(pos2, pos1);

    // 三平方の定理で比較(SqrMagnitudeと同じ)
    float dis = (diff.x * diff.x) + (diff.y * diff.y) + (diff.z * diff.z);
    if (dis < (radius * radius))
    {
        ret = true;
    }

    return ret;
}

void Stage::SetHitPolygon(VECTOR hitPos)
{
    //y軸方向に減少する
	VECTOR down = { 0.0f,-50.0f,0.0f };

    //減少させる
	for (auto& p : polygons_) 
	{
		for (int i = 0; i < 3; i++)
		{
            //プレイヤーの着地点から半径100の範囲にポリゴンの頂点があったら
			if (IsHitSphere(hitPos, 100.0f, p.vertexs[i].pos))
			{
                //座標減少
				p.vertexs[i].pos = VAdd(p.vertexs[i].pos, down);
				vertexes_[p.idx[i]].pos = p.vertexs[i].pos;
			}
		}
	}
}
```

### 作成した関数で値を渡す
プレイヤークラスで先ほどの関数を呼び出し、情報を渡します。
```c++:Player.cpp
void CollisionTriangle(void){
//AABBの当たり判定
//~省略~
                // 三角形と線分の衝突判定
                //~省略~
    			if(hit.HitFlag > 0 && VDot(dirGravity, jumpPow_) > 0)
				{
                    //落下したときのみ
    				if (isJump_)
    				{
    					stage_->SetHitPolygon(hit.Position);
    				}
                    //~省略~
				}
			}
		}
	}
}
```
出力はこうなります。
![dent.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3402790/abc6e06a-d93e-b452-8ca9-2474ad0c5602.gif)

これでめちゃくちゃ強引にポリゴンを変動できるようになりました。
もっと良い実装方法やデータ管理の仕方等はあると思いますが、今回はこれで完成とします。

# 参考
ゲームつくろー！　衝突判定編　その11 AABBと点の最短距離
http://marupeke296.com/COL_3D_No11_AABBvsPoint.html