[Unity] Plane Meshを別のモデルの形状に沿わせる


落下地点をマーキングするなど、なにかしらのインジケータとして地面に沿うようになにかを表示したい、という要望があるかと思います。（というかまんまそれがやりたかったんですがｗ）

こちらの記事（[WORKING WITH UNITY3D ENGINE – DECAL EDITOR](https://davidarayan.wordpress.com/2013/10/16/working-with-unity3d-engine-decal-editor/)）を見つけ、Raycastを使ってやってるのかなーと思いたち、実装してみました。

動きとしてはこんな感じになります。

![Gyazo](https://i.gyazo.com/4cb4d2cadcc5bff07b140780aaff380c.gif)]
*実際に地面に沿うところ*

## 処理の流れ

今回作成したものの流れは以下です。

1. ランタイムで新しく`GameObject`を生成する
2. 各種、表示に必要なコンポーネントを追加
3. スクリプトをアタッチされたオブジェクトの`Mesh`情報をコピー
4. `Update`内で頂点情報を更新

という流れになります。

## コード

コードを順番に見ていきます。

### GameObjectを生成する

まずは空の`GameObject`を生成します。
最終的にこのオブジェクトの頂点情報を更新していきます。

```c#
// Create a projected mesh base.
void CreateProjectedObject() {
    m_projectedOject = new GameObject("ProjectedObject");

    MeshRenderer newRenderer = m_projectedOject.AddComponent<MeshRenderer>();
    MeshFilter newMeshFilter = m_projectedOject.AddComponent<MeshFilter>();
    
    // Copy a mesh to the game object.
    MeshFilter meshFilter = GetComponent<MeshFilter>();
    Mesh       mesh       = meshFilter.mesh;
    
    newMeshFilter.mesh = mesh;

}
```

`new GameObject()` で新規にゲームオブジェクトを生成します。これを実行した時点で自動的にSceneのルートに配置されます。
今回は親子関係を作る必要がないため親は設定しません。

次に、画面に表示するのに必要なコンポーネントを追加します。
具体的には `MeshRenderer` と `MeshFilter` です。

そして追加された `MeshFilter` の `Mesh` に生成元のオブジェクトのメッシュ情報をコピーします。
（頂点情報やindex、レンダリングタイプなど諸々設定する必要があるため）

以上でセットアップは終わりです。


### Updateのタイミングで頂点情報を更新

あとは `Update` のタイミングで頂点情報を更新してやれば完成です。

計算の手順としては以下です。

1. 元のオブジェクトの頂点を取り出す
2. 取り出した頂点をワールド空間座標に変換する
3. 各頂点をいったん上空に起き、そこから下向きに `Ray` を飛ばす
4. ヒットしたらその位置を頂点の高さに設定（ヒットしなかったら `0` にする）
5. 再計算された頂点情報を、投影用オブジェクトの頂点として再設定

という流れです。


#### 頂点をワールド空間座標に変換する

頂点を変換するには以下のようにします。

```c#
// 変換用行列を取り出す
Matrix4x4  matrix = meshFilter.transform.localToWorldMatrix;

// 行列を頂点に適用する
Vector3 vec = matrix.MultiplyPoint(vertex);
```


#### Rayを飛ばして位置を算出する

頂点が変換できたら、全頂点に対して以下のようにRayを飛ばし、位置を判定します。

```c#
foreach (Vector3 vertex in verticies) {
    Vector3 vec = matrix.MultiplyPoint(vertex);
    vec.y = 1000;

    RaycastHit hit;
    if (Physics.Raycast(vec, Vector3.down, out hit)) {
        Vector3 newVert = vec;
        newVert.y = hit.point.y;
        newVerticies[i] = newVert;
    }
    else {
        vec.y = 0;
        newVerticies[i] = vec;
    }

    i++;
}
```

#### Updateメソッドコード全文

まとめると、頂点位置計算の処理は以下のようになります。

```c#
// Update is called once per frame
void Update () {
    MeshFilter meshFilter   = GetComponent<MeshFilter>();
    Matrix4x4  matrix       = meshFilter.transform.localToWorldMatrix;
    Mesh       mesh         = meshFilter.mesh;
    Vector3[]  verticies    = mesh.vertices;
    Vector3[]  newVerticies = new Vector3[verticies.Length];

    int i = 0;
    foreach (Vector3 vertex in verticies) {
        Vector3 vec = matrix.MultiplyPoint(vertex);
        vec.y = 1000;

        RaycastHit hit;
        if (Physics.Raycast(vec, Vector3.down, out hit)) {
            Vector3 newVert = vec;
            newVert.y = hit.point.y;
            newVerticies[i] = newVert;
        }
        else {
            vec.y = 0;
            newVerticies[i] = vec;
        }

        i++;
    }

    Mesh projectedMesh = m_projectedOject.GetComponent<MeshFilter>().mesh;
    projectedMesh.vertices = newVerticies;
    
    // 法線情報の更新が必要な場合は以下を実行する
    // projectedMesh.RecalculateNormals();
}
```

## 最終コード

動作するコード全文を載せておきます。

```c#:ProjectedMesh.cs
using UnityEngine;
using System.Collections;

public class ProjectedMesh : MonoBehaviour {

	// Use for projected mesh.
	private GameObject m_projectedOject;

	// Use this for initialization
	void Start () {
		CreateProjectedObject();
		GetComponent<MeshRenderer>().enabled = false;
	}


	// Update is called once per frame
	void Update () {
		MeshFilter meshFilter   = GetComponent<MeshFilter>();
		Matrix4x4  matrix       = meshFilter.transform.localToWorldMatrix;
		Mesh       mesh         = meshFilter.mesh;
		Vector3[]  verticies    = mesh.vertices;
		Vector3[]  newVerticies = new Vector3[verticies.Length];

		int i = 0;
		foreach (Vector3 vertex in verticies) {
			Vector3 vec = matrix.MultiplyPoint(vertex);
			vec.y = 1000;

			RaycastHit hit;
			if (Physics.Raycast(vec, Vector3.down, out hit)) {
				Vector3 newVert = vec;
				newVert.y = hit.point.y;
				newVerticies[i] = newVert;
			}
			else {
				vec.y = 0;
				newVerticies[i] = vec;
			}

			i++;
		}

		Mesh projectedMesh = m_projectedOject.GetComponent<MeshFilter>().mesh;
		projectedMesh.vertices = newVerticies;
		// projectedMesh.RecalculateNormals();
	}

	// Create a projected mesh base.
	void CreateProjectedObject() {
		m_projectedOject = new GameObject("ProjectedObject");

		MeshRenderer newRenderer = m_projectedOject.AddComponent<MeshRenderer>();
		MeshFilter newMeshFilter = m_projectedOject.AddComponent<MeshFilter>();
		
		// Copy a mesh to the game object.
		MeshFilter meshFilter = GetComponent<MeshFilter>();
		Mesh       mesh       = meshFilter.mesh;
		
		newMeshFilter.mesh = mesh;
	}
}
```

## 参考にした記事

- [UnityでありもののMeshをワイヤフレームで描画する](http://izmiz.hateblo.jp/entry/2014/02/27/224651)
- [Physics.Raycast | Unity - スクリプトリファレンス](http://docs.unity3d.com/jp/current/ScriptReference/Physics.Raycast.html)
- [Mesh.RecalculateNormals | Unity - スクリプトリファレンス](http://docs.unity3d.com/ScriptReference/Mesh.RecalculateNormals.html)
- [Mesh | Unity - スクリプトリファレンス](http://docs.unity3d.com/ja/current/ScriptReference/Mesh.html)
- [How do you create an empty gameobject in code and add it to the scene? - Unity Answer](http://answers.unity3d.com/questions/572852/how-do-you-create-an-empty-gameobject-in-code-and.html)