C#でひし形画像の内外判定をしてみる

# 1. はじめに
* C#のWindowsForm内の画像（ひし形）に対してクリック判定をしたい
* 画像内のひし形の４点の座標から内外判定をしてイベントを起動したい

# 2. 開発環境
* C# (Windows Form)
* Visual Studio 2022
* Windows 11

# 3. 動作イメージ
* 赤色、青色の枠内をクリックするとイベントと発生される
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2852081/7f5e53c5-4c0d-8ed0-6981-98454d98a1a1.png)

* 右クリックでラベル表示、左クリックでラベル非表示にする
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2852081/90ac8957-0eca-fbb1-cc6b-1a9ffe36fa4f.png)

# 4. プログラムソース
## 4.1. デザイン
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2852081/08abdf90-2ff1-216a-6a40-9c681a86b2e7.png)

|コントロール  |Name  |備考  |
|---|---|---|
|PictureBox  |PictureBox1  |画像を配置  |
|Label  |labelMark1  |青色上のマーク  |
|Label  |labelMark2  |赤い色上のマーク  |

* 画面右のX,Y座標はマウスの移動時に表示するようデバッグ用で配置

## 4.2. コードビハインド
```csharp:Form1.cs
using System.Diagnostics;

namespace WinFormsApp1
{
    public partial class Form1 : Form
    {
        // ひし形1 頂点A～D
        // 赤色のひし形の座標を時計回りに指定する
        Point2f pa1 = new Point2f(32, 3);
        Point2f pb1 = new Point2f(95, 33);
        Point2f pc1 = new Point2f(75, 87);
        Point2f pd1 = new Point2f(4, 59);

        // ひし形2 頂点A～D
        // 青色のひし形の座標を時計回りに指定する
        Point2f pa2 = new Point2f(95, 33);
        Point2f pb2 = new Point2f(169, 64);
        Point2f pc2 = new Point2f(144, 123);
        Point2f pd2 = new Point2f(75, 87);

        public Form1()
        {
            InitializeComponent();
        }

        // 単純な2点の情報を表すクラス
        public class Point2f
        {
            public float X { get; set; }
            public float Y { get; set; }

            public Point2f(float x, float y)
            {
                this.X = x;
                this.Y = y;
            }

            public override string ToString() => $"({this.X}, {this.Y})";
        }

        // 衝突判定系の汎用処理クラス
        public static class CollisionUtil
        {
            // 指定した4点の中にpがあるかどうかを判定する
            // 但し、4つそれぞれの内角のいずれかが180度以上の場合、正しく判定できない。
            public static bool IsInSquare(Point2f pa, Point2f pb, Point2f pc, Point2f pd, Point2f p)
            {
                float a = CalcExteriorProduct(pa, pb, p);
                float b = CalcExteriorProduct(pb, pc, p);
                float c = CalcExteriorProduct(pc, pd, p);
                float d = CalcExteriorProduct(pd, pa, p);

                Debug.WriteLine($"{a}, {b}, {c}, {d}");

                bool res = a > 0 && b > 0 && c > 0 && d > 0;

                return res;
            }

            // 指定した2点間と1点の外積を計算する
            public static float CalcExteriorProduct(Point2f a, Point2f b, Point2f p)
            {
                // 点 a,b のベクトル
                var vecab = new Point2f(a.X - b.X, a.Y - b.Y); // ここは固定なら最初から計算しておくのもアリかも
                                                               // 点a と 点のベクトル
                var vecpa = new Point2f(a.X - p.X, a.Y - p.Y);
                // 外積を計算する
                float ext = vecab.X * vecpa.Y - vecpa.X * vecab.Y;

                return ext;
            }
        }

        private void pictureBox1_MouseDown(object sender, MouseEventArgs e)
        {
            // 現在のX座標、Y座標を表示する
            textBoxX.Text = e.X.ToString();
            textBoxY.Text = e.Y.ToString();

            // ひし形1の内外判定を行う
            if (CollisionUtil.IsInSquare(pa1, pb1, pc1, pd1, new Point2f(e.X, e.Y)))
            {
                switch (e.Button)
                {
                    case MouseButtons.Left:
                        // 左クリックの場合：ラベル表示
                        labelMark1.Visible = true;
                        break;
                    case MouseButtons.Right:
                        // 右クリックの場合：ラベル非表示
                        labelMark1.Visible = false;
                        break;
                    default:
                        break;
                }
            }

            // ひし形2の内外判定を行う
            if (CollisionUtil.IsInSquare(pa2, pb2, pc2, pd2, new Point2f(e.X, e.Y)))
            {
                switch (e.Button)
                {
                    case MouseButtons.Left:
                        // 左クリックの場合：ラベル表示
                        labelMark2.Visible = true;
                        break;
                    case MouseButtons.Right:
                        // 右クリックの場合：ラベル非表示
                        labelMark2.Visible = false;
                        break;
                    default:
                        break;
                }
            }

        }
    }

}
```

## 4.3. イベント
* PictureBox,各Labelの下記イベントに```pictureBox1_MouseDown```を設定する

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2852081/c47babb5-4942-ae56-5803-7429d2d2495f.png)

# 5. 参考文献

https://takap-tech.com/entry/2018/07/24/225933

https://hiros-dot.net/CS2003/Mouse/Mouse01.htm