Unity - パ○ツを遮る謎の光をより厳密に改造してみた。

# 概要
[偉大な先人](https://qiita.com/kamanii24/items/0e4825b7a3406d7d3332)がいた。
彼に尊敬の念をこめて、より厳密にパ○ツ判定ができるようにした。
ロマンを求めたかった。

# 先人の問題点
- スカートにColliderを設置する必要がある。
- スカートはclothだがColliderはblockなため、厳密には形が異なる。また、Collider設定者の力量に依存する。
- 障害物に対応したい。

ちゃんと見えるはずの時のみ謎の光は活躍すべきである！（？）。

# 方針
「見える時」とは、カメラとパ○ツとの間に障害が何もない時である。
例えパ○ツが無限遠方にあっても見えるものである。
まさに奇跡である。

実際の流れは以下
1. モデルにMeshColliderをつける。
2. カメラに以下のRayToMeshスクリプトをつける。
3. 見えそうな感じに動かしてみる。

使用モデルはプロ生ちゃん（感謝）

# 結果
![screen.gif](https://qiita-image-store.s3.amazonaws.com/0/72975/0d77b7e3-0320-8b6a-bece-ce806bd111fe.gif)
見える時に光が出て、障害物で遮ると光は消える。


# 実装
スクリプトの内容
1. カメラからキャラクターに向かってRayを飛ばす
2. 当たった点に対応するMeshとTextureを取得する
3. 当たった点がTextureにおいてUV座標のどこに相当するかを求める。
4. UV座標がパ○ツ領域かを求める。
5. パ○ツ領域なら謎の光発動！！

```csharp
/// <summary>
/// パンツマスクを用いてパンツ判定
/// </summary>
public class RayToMesh : MonoBehaviour
{
    // シーン内対象キャラクター
    [SerializeField] private SkinnedMeshRenderer targetMesh;
    // Mask用Texture
    [SerializeField] private Texture2D maskTexture;
    // 謎の光
    [SerializeField] private LensFlare lansFlare;

    /// <summary>
    /// 毎フレーム処理
    /// Rayを飛ばし当たり判定検出
    /// </summary>
    void Update()
    {
        // 一旦謎の光は待機
        lansFlare.brightness = 0;

        var matrix = targetMesh.transform.localToWorldMatrix;
        // submeshのうち、パンツマテリアルがあるmeshのみ取得
        foreach (var vertex in targetMesh.sharedMesh.vertices.Skip((int)targetMesh.sharedMesh.GetIndexStart(0)).Take((int)targetMesh.sharedMesh.GetIndexStart(1)))
        {
            // 頂点座標変換
            var vec = matrix.MultiplyPoint(vertex);
            RaycastHit hit;
            // カメラからのRayで衝突判定
            if (Physics.Raycast(transform.position, vec - transform.position, out hit))
            {
                // UV取得
                var texCoord = hit.textureCoord;
                if (texCoord.x > 0 && texCoord.y > 0)
                {
                    // マスク画像に対するRayの当たった場所の色を取得する
                    // 非透明 = パンツ領域とする
                    var maskCol = maskTexture.GetPixel((int)(texCoord.x * maskTexture.width), (int)(texCoord.y * maskTexture.height));
                    if (maskCol.a > 0)
                    {
                        // 当たっていることを明示してみる
                        // Debug.DrawLine(transform.position, vec, color: Color.white, duration: 1000f);

                        // 中間点に謎の光表示
                        lansFlare.transform.position = Vector3.Lerp(vec, transform.position, 0.5f);
                        lansFlare.brightness = 0.4f;
                        return;
                    }
                }
            }
        }
    }
}
```



# 補足

Mask用Textureは元のTextureからパ○ツ領域にだけ色を塗った画像である（パ○ツマスク）。
UV画像は分かりにくいがTextureを塗りつぶしながら「ここが当たりかな？」とかやって領域を見つける。
ちなみに使用したパ○ツマスク。dress画像の右上がそうらしい。このdressは全体の服にも使われている。
<img src="https://qiita-image-store.s3.amazonaws.com/0/72975/a1c22687-20b8-4106-9382-7c57aaae221d.png" width=30%>


Meshの頂点を取ってくる際、プロ生ちゃんは1つのMeshが複数のMeshで構成されている。
そのためパ○ツに対するSubMesh判定が必要である。
幸いにもパ○ツ領域は1つのMaterialで構成され、MaterialのIndexが0番だった（匠の技）。
一応複数対応できるようにLinqのSkipとTakeを使った。

Rayを頂点分飛ばすことになるのでUpdate内の処理では不適切。
n頂点のうち1頂点だけ判定するなど精度を落としたり、何かが動いた瞬間に判定してタイミングを制限したりすると実用化できそう。
Rayでの判定のため障害物（Cube）が間に入ってきたら謎の光は発動しない。

textureCoordとはUV座標のこと。
Rayが当たった場所がTextureのUV座標においてどこに相当するかを取得できる。
このTextureとパ○ツマスクは元が全く同じなので、UV座標→テクスチャ座標→色取得を行い透明度を検出する。

# 最後に
もしライセンスや規約違反の場合は速攻消させていただきます。申し訳ございません。