4. 凸形直線環繞系統

好抽象@@

using System.Collections;
using System.Drawing.Drawing2D;

namespace Q4
{

    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        Random rd = new Random();
        List<Rectangle> blocks = new List<Rectangle>();
        bool advance = false;

        private void button3_Click(object sender, EventArgs e)
        {
            Close();
        }

        private void button1_Click(object sender, EventArgs e)
        {
            advance = false;
            blocks.Clear();
            for (int i = 0, v = rd.Next(3, 5); i < v; i++)
            {
                Rectangle rect;
                do
                {
                    int y = rd.Next(20, 81);
                    int ys = rd.Next(40, 201);
                    rect = new Rectangle(rd.Next(20, 81), 400 - ys - y, rd.Next(40, 201), ys);
                }
                while (blocks.Count != 0 && !blocks.Any(e => e.IntersectsWith(rect)));

                blocks.Add(rect);
            }
            panel1.Refresh();
            panel2.Refresh();
        }

        private void panel1_Paint(object sender, PaintEventArgs e)
        {
            for (int i = 0; i < blocks.Count; i++)
            {
                var r = blocks[i];
                var p = new Pen(Brushes.Black, 2);
                p.DashStyle = (DashStyle)i;
                e.Graphics.DrawRectangle(p, r.X, r.Y, r.Width, r.Height);
            }
        }

        //------------------------------------------------------------------
        //   ★★★ 正確:求凸形直線環繞系統(Convex Rectilinear Surround) ★★★
        //------------------------------------------------------------------

        public record Seg(Point A, Point B);

        private List<Seg> GetConvexRectilinearSurround(List<Rectangle> blocks)
        {
            List<Seg> edges = new();
            if (blocks.Count == 0) return edges;

            // Step 1: 收集所有 X/Y 分界線,形成網格座標
            // x/y 0 3 9 27
            // 1
            // ...
            // 7
            // ...
            // 9
            var xs = blocks.Select(b => b.Left)
                           .Concat(blocks.Select(b => b.Right))
                           .Distinct()
                           .OrderBy(v => v)
                           .ToList();

            var ys = blocks.Select(b => b.Top)
                           .Concat(blocks.Select(b => b.Bottom))
                           .Distinct()
                           .OrderBy(v => v)
                           .ToList();

            int W = xs.Count - 1;
            int H = ys.Count - 1;

            // cell[i,j] 表示 [xs[i], xs[i+1]) x [ys[j], ys[j+1]) 是否被覆蓋
            bool[,] cell = new bool[W, H];

            // Step 2: 將原本所有矩形打到 cell 中
            foreach (var r in blocks)
            {
                for (int i = 0; i < W; i++)
                {
                    if (xs[i + 1] <= r.Left || xs[i] >= r.Right) continue;

                    for (int j = 0; j < H; j++)
                    {
                        if (ys[j + 1] <= r.Top || ys[j] >= r.Bottom) continue;
                        cell[i, j] = true;
                    }
                }
            }

            // Step 3: X 方向凸化(每一列)
            for (int j = 0; j < H; j++)
            {
                int left = -1, right = -1;
                for (int i = 0; i < W; i++)
                {
                    if (cell[i, j])
                    {
                        if (left == -1) left = i;
                        right = i;
                    }
                }
                if (left != -1)
                {
                    for (int i = left; i <= right; i++)
                        cell[i, j] = true;
                }
            }

            // Step 4: Y 方向凸化(每一欄)
            for (int i = 0; i < W; i++)
            {
                int top = -1, bottom = -1;
                for (int j = 0; j < H; j++)
                {
                    if (cell[i, j])
                    {
                        if (top == -1) top = j;
                        bottom = j;
                    }
                }
                if (top != -1)
                {
                    for (int j = top; j <= bottom; j++)
                        cell[i, j] = true;
                }
            }

            // Step 5: 從凸化後的 cell 中取真正外框(只取 true/false 的交界)
            for (int x = 0; x < W; x++)
            {
                for (int y = 0; y < H; y++)
                {
                    if (!cell[x, y]) continue;

                    int x0 = xs[x];
                    int x1 = xs[x + 1];
                    int y0 = ys[y];
                    int y1 = ys[y + 1];

                    // 上邊: y在邊邊 or 上方沒填滿
                    if (y == 0 || !cell[x, y - 1])
                        edges.Add(new Seg(new Point(x0, y0), new Point(x1, y0)));

                    // 下邊
                    if (y == H - 1 || !cell[x, y + 1])
                        edges.Add(new Seg(new Point(x0, y1), new Point(x1, y1)));

                    // 左邊
                    if (x == 0 || !cell[x - 1, y])
                        edges.Add(new Seg(new Point(x0, y0), new Point(x0, y1)));

                    // 右邊
                    if (x == W - 1 || !cell[x + 1, y])
                        edges.Add(new Seg(new Point(x1, y0), new Point(x1, y1)));
                }
            }

            return edges;
        }

        //------------------------------------------------------------------

        private void panel2_Paint(object sender, PaintEventArgs e)
        {
            if (!advance) return;

            // 畫原本矩形
            for (int i = 0; i < blocks.Count; i++)
            {
                Rectangle item = blocks[i];
                var p = new Pen(Brushes.Black, 2);
                p.DashStyle = (DashStyle)i;
                e.Graphics.DrawRectangle(p, item.X, item.Y, item.Width, item.Height);
            }

            // ★ 取得凸形直線外框
            var edges = GetConvexRectilinearSurround(blocks);

            using (var pen = new Pen(Color.Red, 4))
            {
                foreach (var s in edges)
                    e.Graphics.DrawLine(pen, s.A, s.B);
            }
        }

        private void button2_Click(object sender, EventArgs e)
        {
            advance = true;
            panel2.Refresh();
        }
    }
}

Bug版,共線會有問題,但用的是題目的方法@@

Last updated