ポリゴン選択

#はじめに

久しぶりに軽めの記事を書いていきます。
大雑把にポリゴン選択についてといったタイトルでやってます。
これは、形状選択をする際に、オブジェクトごとに、選択したい場合と、
オブジェクトの三角形ごとに選択したい場合があるかなと思い、2つ兼ねて今回説明しようと思ったからです。方法としては、交差判定を用いる方法と、IDを用いる方法の2種類です。
自分は三角形選択は交差判定を用いるやり方、オブジェクト選択はIDを用いるやり方でやってますが
実行速度とか気にしなければどっちでも良いかと思います。


##交差判定を用いるやり方
交差判定を用いるやり方は、Perspectiveカメラをもとに説明していきます。
PerspectiveカメラのViewportに表示されている画面は、カメラのNear以上、Far以下の領域に
なっていることは知ってるかと思います。
![pers.png](https://qiita-image-store.s3.amazonaws.com/0/63344/b7415f60-6dfc-1e59-67f7-b3a09b6ed250.png)

こんなやつですね。絵は下手です。
この台形のやつの内側にある、オブジェクトが描画領域として、表示されてます。
円形がこんな感じでです。
![pers.png](https://qiita-image-store.s3.amazonaws.com/0/63344/29700edb-4f3c-d0d5-50cc-d4ca58ede639.png)

これで画面の真ん中には赤い球体が描画されている状態になります。

ここから、球体を構成する三角形を選択しましょう。
考え方はこんな感じです。
![pers.png](https://qiita-image-store.s3.amazonaws.com/0/63344/8119efa9-79c1-b5ae-84ae-a3bdde3dbf61.png)
Near面でのマウス位置とFar面でのマウス位置から、ベクトルを作り、そのベクトルと交差する三角形が選択されたものと判定できます。
Near面とFar面について簡単に詳細を。
Near面は、3次元では、Zが0の位置にあります。マウス位置と合わせると
Vector3(Mouse.X,Mouse.Y,0）として計算していきます。
一方、Far面はZが1の位置にあります。そのため、Vector3（Mouse.X,Mouse.Y,1）として計算します。これらの値を用いて、openglでいうUnProjectの処理を行います。
実際3次元形状を描画するときには、MVP行列（ModelMatrix * ViewMatrix * ProjectMatrix）
を使って、描画しているものの逆処理をするやつです。
先程のVector3(Mouse.X,Mouse.Y,0)、Vector3(Mouse.X,Mouse.Y,1)をUnProjectするのです。
これで、ベクトルが算出できますのであとはベクトルと三角形の交差判定をするだけです。
交差判定をして当たった三角形が選択されている三角形です。
ということでコード

Nearの位置とFarの位置をUnProjectします。
viewportには、ViewportのGL.Viewport（x,y,w,h）の値が入ってます。

```csharp
        public static void GetClipPos(Matrix4 cameraMatrix, Matrix4 projMatrix, int[] viewport, Vector2 mouse, out Vector3 near, out Vector3 far)
        {
            near = UnProject(new Vector3(mouse.X, mouse.Y, 0.0f), cameraMatrix, projMatrix, viewport);
            far = UnProject(new Vector3(mouse.X,mouse.Y,1.0f),cameraMatrix,projMatrix,viewport);
        }
```

UnProjectの中身。
ずっと前に書いたので忘れました。まぁUnProjectしてます。

```csharp
        public static Vector3 UnProject(Vector3 mouse, Matrix4 cameraMatrix, Matrix4 projMatrix, int[] viewPort)
        {
            Vector4 projPos = new Vector4(mouse, 1.0f);
            Matrix4 modelproj = cameraMatrix * projMatrix;
            modelproj.Invert();
            Vector4 ret = new Vector4();

            projPos.X = (mouse.X - viewPort[0]) / (float)viewPort[2];
            projPos.Y = (mouse.Y - viewPort[1]) / (float)viewPort[3];

            projPos.X = (projPos.X * 2) - 1.0f;
            projPos.Y = (projPos.Y * 2) - 1.0f;
            projPos.Z = (projPos.Z * 2) - 1.0f;
            projPos.Y *= -1;
            projPos.W = 1.0f;

            ret = Vector4.Transform(projPos, modelproj);
            if (ret.W == 0.0f)
            {
                return Vector3.Zero;
            }

            ret.X /= ret.W;
            ret.Y /= ret.W;
            ret.Z /= ret.W;
            return new Vector3(ret.X, ret.Y, ret.Z);
        }
```



これらの値をもとに、交差判定して、最小距離を取ってます。
CrossPlanetoLinePosで交差判定して一番近いポリゴンを取得してます。
交差判定は結構長かったので割愛します。

```csharp
 CCalc.GetClipPos(MainCamera.Matrix, MainCamera.ProjMatrix, viewport, mouse, out near, out far);
 for (int i = 0; i < geometry.Position.Count / 3; i++)
                    {
                        Vector3 vertex1 = CCalc.Multiply(geometry.ModelMatrix, geometry.Position[3 * i]);
                        Vector3 vertex2 = CCalc.Multiply(geometry.ModelMatrix, geometry.Position[3 * i + 1]);
                        Vector3 vertex3 = CCalc.Multiply(geometry.ModelMatrix, geometry.Position[3 * i + 2]);
                        Vector3 result = Vector3.Zero;
                        if (CCalc.CrossPlanetoLinePos(vertex1, vertex2, vertex3, near, far, ref minLength, out result))
                        {
                            if (minTriangle == null)
                            {
                                minTriangle = new List<Vector3>();
                                minTriangle.Add(vertex1);
                                minTriangle.Add(vertex2);
                                minTriangle.Add(vertex3);
                            }
                            else
                            {
                                minTriangle[0] = vertex1;
                                minTriangle[1] = vertex2;
                                minTriangle[2] = vertex3;
                            }
                        }
                    }
```


##オブジェクト毎の選択
続いてオブジェクト毎の選択です。
オブジェクト毎の選択はすごく簡単です。
<b>1.オブジェクト毎に違う色で描画します。</b>
<b>2.クリックした位置の色を取得します。</b>
<b>3.クリックした位置の色と同じ色のオブジェクトが選択したオブジェクトです。</b>
以上です。とっても簡単です。

しかし、2番について少し補足しときます。
こういったやり方を取る場合は、fboとかに書く場合もあるかと思います。
その際、GL.ReadPixelsでの取得どうすんの？といったときのためにコードをペタリ
自分はGBufferのColorAttachment1のalpha成分に（オブジェクト番号/255）を割り当てて選択しているので、こんな感じになります。まぁBindしとけばOKなんだねっていうことです。

```csharp
            GL.BindFramebuffer(FramebufferTarget.Framebuffer, GBuffer.FrameId);
            GL.ReadBuffer(ReadBufferMode.ColorAttachment1);
            float[] pixels = new float[4];
            GL.ReadPixels(x, y, 1, 1, PixelFormat.Rgba, PixelType.Float, pixels);
            GL.ReadBuffer(ReadBufferMode.None);
            GL.BindFramebuffer(FramebufferTarget.Framebuffer, 0);
            int id = (int)(pixels[3] * 255);
            foreach(var geometry in AssetFactory.Instance.assetList.Values.OfType<Geometry>())
            {
                if(geometry.ID == id)
                {
                    Scene.ActiveScene.SelectAsset = geometry;
                    break;
                }
            }
```

これで、指定したfboの色情報が取れるので、その色情報から、選択形状を判定しましょう。