より良いエンジニアを目指して

1日1つ。良くなる!上手くなる!

ジグザグ線を描画するには

ジグザグ線を描画するにはどうすればいいか?

結論に入る前に以下の画像を見てもらうとイメージしやすいです。

f:id:rimever:20200812190541p:plain
青がジグザグ線。赤が補線です。

  • 始点と終点を等間隔の点を取る
  • 点同士と正三角形となる点を中間地点に入れて折れ線としながら結んでいく。

ジグザグの折れている点をその前後の点で正三角形を作るのです。正三角形は、3点の距離が等しい。

というわけで、綺麗なジグザグになるわけです。

Let's Code

MainWindow.xaml

<Window x:Class="DrawZigZagLine.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:DrawZigZagLine"
        mc:Ignorable="d"
        Title="MainWindow" Height="600" Width="800">
    <Grid>
<Canvas x:Name="Canvas"/>
    </Grid>
</Window>

MainWindow.xaml.cs

#region

using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows;
using System.Windows.Media;
using System.Windows.Shapes;

#endregion

namespace DrawZigZagLine
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            {
                var polyLine = new Polyline();
                var points = new PointCollection
                {
                    new Point(100, 100),
                    new Point(700, 500)
                };
                polyLine.Points = points;
                polyLine.Stroke = Brushes.Red;
                Canvas.Children.Add(polyLine);
            }
            {
                var polyLine = new Polyline();
                var points = new PointCollection();
                foreach (var point in EnumerableZigZagPoints(new Point(100, 100), new Point(700, 500)))
                {
                    points.Add(point);
                }

                polyLine.Points = points;
                polyLine.Stroke = Brushes.Blue;
                Canvas.Children.Add(polyLine);
            }
        }

        private IEnumerable<Point> EnumerableZigZagPoints(Point startPoint, Point endPoint)
        {
            var span = 20d;
            var count = Math.Sqrt(Math.Pow(endPoint.X - startPoint.X, 2) + Math.Pow(endPoint.Y - startPoint.Y, 2)) /
                        span;
            var spanPoints = new List<Point>();
            for (int i = 0; i < count; i++)
            {
                var point = new Point
                {
                    X = startPoint.X + (endPoint.X - startPoint.X) * i / count,
                    Y = startPoint.Y + (endPoint.Y - startPoint.Y) * i / count
                };
                spanPoints.Add(point);
            }
            yield return startPoint;

            for (int i = 0; i < spanPoints.Count - 1; i++)
            {
                var previousPoint = spanPoints[i];
                var nextPoint = spanPoints[i + 1];
                yield return GetZigZagPoint(previousPoint, nextPoint);
                yield return nextPoint;
            }

            yield return endPoint;
        }

        /// <summary>
        /// 2点の間のジグザグ線の折れ点となる座標を算出します
        /// </summary>
        /// <param name="previousPoint"></param>
        /// <param name="nextPoint"></param>
        /// <returns></returns>
        private static Point GetZigZagPoint(Point previousPoint, Point nextPoint)
        {
            var cx = nextPoint.X - previousPoint.X;
            var cy = nextPoint.Y - previousPoint.Y;
            var angle = 60 * Math.PI / 180;
            var x = cx * (float) Math.Cos(angle) - cy * (float) Math.Sin(angle) + previousPoint.X;
            var y = cx * (float) Math.Sin(angle) + cy * (float) Math.Cos(angle) + previousPoint.Y;
            return new Point(x, y);
        }
    }
}

なんかセコくない?

本来の始点と終点の直線上から片側せり出しただけなので、手抜き感はあります。

きちんと両側に突き出したいんだ。

となれば、各点を少しずらすのはどうでしょう。

MainWindow.xaml.cs

#region

using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows;
using System.Windows.Media;
using System.Windows.Shapes;

#endregion

namespace DrawZigZagLine
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            {
                var polyLine = new Polyline();
                var points = new PointCollection
                {
                    new Point(100, 100),
                    new Point(700, 500)
                };
                polyLine.Points = points;
                polyLine.Stroke = Brushes.Red;
                Canvas.Children.Add(polyLine);
            }
            {
                var polyLine = new Polyline();
                var points = new PointCollection();
                foreach (var point in EnumerableZigZagPoints(new Point(100, 100), new Point(700, 500)))
                {
                    points.Add(point);
                }

                polyLine.Points = points;
                polyLine.Stroke = Brushes.Blue;
                Canvas.Children.Add(polyLine);
            }
        }

        private IEnumerable<Point> EnumerableZigZagPoints(Point startPoint, Point endPoint)
        {
            var span = 20d;
            var count = Math.Sqrt(Math.Pow(endPoint.X - startPoint.X, 2) + Math.Pow(endPoint.Y - startPoint.Y, 2)) /
                        span;
            var spanPoints = new List<Point>();
            for (int i = 0; i < count; i++)
            {
                var point = new Point
                {
                    X = startPoint.X + (endPoint.X - startPoint.X) * i / count,
                    Y = startPoint.Y + (endPoint.Y - startPoint.Y) * i / count
                };
                spanPoints.Add(point);
            }

            var diffZigZagPoint = spanPoints.Count >= 2
                ? GetZigZagPoint(new Point()
                    ,new Point()
                    {
                        X = spanPoints.Skip(1).First().X - spanPoints.First().X,
                        Y = spanPoints.Skip(1).First().Y - spanPoints.First().Y
                    })
                : new Point();
            // 「2点の中間地点」と「折れ点」の中間距離を割り出しておき、その分、ずらすのに使う
            var slideZigZagPoint = new Point()
            {
                X = ((spanPoints.Skip(1).First().X - spanPoints.First().X) / 2 - diffZigZagPoint.X) / 2,
                Y = ((spanPoints.Skip(1).First().Y - spanPoints.First().Y) / 2 - diffZigZagPoint.Y) / 2
            };

            yield return startPoint;
            for (int i = 0; i < spanPoints.Count - 1; i++)
            {
                var previousPoint = spanPoints[i];
                var nextPoint = spanPoints[i + 1];
                yield return new Point()
                {
                    X = previousPoint.X + diffZigZagPoint.X + slideZigZagPoint.X,
                    Y = previousPoint.Y + diffZigZagPoint.Y + slideZigZagPoint.Y
                };
                yield return new Point()
                {
                    X = nextPoint.X + slideZigZagPoint.X,
                    Y = nextPoint.Y + slideZigZagPoint.Y,
                };
            }

            yield return endPoint;
        }

        /// <summary>
        /// 2点の間のジグザグ線の折れ点となる座標を算出します
        /// </summary>
        /// <param name="previousPoint"></param>
        /// <param name="nextPoint"></param>
        /// <returns></returns>
        private static Point GetZigZagPoint(Point previousPoint, Point nextPoint)
        {
            var cx = nextPoint.X - previousPoint.X;
            var cy = nextPoint.Y - previousPoint.Y;
            var angle = 60 * Math.PI / 180;
            var x = cx * (float) Math.Cos(angle) - cy * (float) Math.Sin(angle) + previousPoint.X;
            var y = cx * (float) Math.Sin(angle) + cy * (float) Math.Cos(angle) + previousPoint.Y;
            return new Point(x, y);
        }
    }
}

f:id:rimever:20200812194410p:plain
It's Okay!

回転の公式

座標の回転の公式の証明はスマートで美しいと思います。

mathwords.net