Help us understand the problem. What is going on with this article?

NGraphicsを使ってドラゴン曲線を描く

はじめに

フラクタル図形の一種であるヘイウェイ・ドラゴン曲線と言われる図を描くプログラムをC#で書いてみました。

ヘイウェイ・ドラゴン曲線とは、例えば以下のような図です。

Wikipediaのドラゴン曲線を参考にしました。

コンソールアプリケーションとして作成し、作成した図形は、pngファイルとして出力しています。

ソースコードの説明

以下、ソースコードの簡単な説明です。

Program クラス

プログラムをつかさどるクラス。DragonCurveとDrawerを使って、図形を描画しています。
ここで、曲線の次数を与えています。

DragonCurve クラス

ドラゴン曲線を描くクラスです。実際の線は引いていません。DragonCurveクラスは、IObservable<T>を実装していて、線を引く代わりに、購読者クラス(Drawer)にその状態の変化を伝えています。
そのため実際の描画という行為からは完全に独立しています。

Drawメソッドは、再帰メソッドになっていて、 引数 generationは、再帰メソッドを呼び出す深さを表しています。 Drawメソッドは、2つの点 p1,p2から次の一点 c を求め線を引き、 (p1,c) (p2,c) それぞれについて、Drawメソッドを再帰的に呼び出すことで、図形を描いてい行きます。

NextPointが、次の点 c を求めるメソッドですが、思いのほか複雑になってしまいました。 もっと簡潔に書けるようなきもするのですが...。

Drawer クラス

描画を受け持つDrawerクラスは、IObserver<T>を実装している購読者クラスです。
DragonCurve からの通知を受け取り、線を描いて行きます。線を描くのに、以下のEasyCanvasクラスを利用しています。

EasyCanvas クラス

NGraphicパッケージを利用して、線を描いています。結果はpngファイルにしています。

ソースコード

Program.cs

using System;
using System.Collections.Generic;
using System.Linq;

namespace DragonCurve
{
    class Program
    {
        static void Main(string[] args)
        {
            int generation = 10;
            int size = 500;
            var dragon = new DragonCurve();
            var drawer = new Drawer(size, size, generation);
            dragon.Subscribe(drawer);
            // 初期の線
            var line = new LineInfo
            {
                Start = new Point(size * 0.25f, size * 0.55f),
                End = new Point(size * 0.75f, size * 0.55f)
            };
            dragon.Start(line, generation);
        }
    }
}

DragonCurve.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace DragonCurve
{
    public class Point
    {
        public float X { get; set; }
        public float Y { get; set; }
        public Point(float x, float y)
        {
            X = x;
            Y = y;
        }
    }

    public class LineInfo
    {
        public Point Start { get; set; }
        public Point End { get; set; }
    }

    public class DragonCurve : IObservable<LineInfo>
    {

        public void Start(LineInfo line, int genelation)
        {
            Draw(line.Start, line.End, genelation);
            foreach (var observer in _observers)
            {
                observer.OnCompleted();
            }
        }

        private void Draw(Point p1, Point p2, int generation)
        {
            if (generation == 0)
            {
                var info = new LineInfo
                {
                    Start = p1,
                    End = p2
                };
                Publish(info);
            }
            else
            {
                Point c = NextPoint(p1, p2);
                Draw(p1, c, generation - 1);
                Draw(p2, c, generation - 1);
            }
        }

        private Point NextPoint(Point p1, Point p2)
        {
            var direction = ToDegree(GetSlope(p1, p2));
            var p3 = new Point(p2.X - p1.X, p2.Y - p1.Y);
            var len = LineLength(p1, p2) / 2;
            var nlen = Math.Sqrt(len * len * 2);
            var nd = (direction + 315) % 360;
            var p4 = new Point(
                        (float)(Math.Cos(ToRadian(nd)) * nlen + p1.X),
                        (float)(Math.Sin(ToRadian(nd)) * nlen + p1.Y));
            return p4;
        }

        private double GetSlope(Point p1, Point p2)
        {
            var vx = p2.X - p1.X;
            var vy = p2.Y - p1.Y;
            var n = Math.Atan(vy / vx);
            if (vx < 0)
                return Math.PI + n;    // 180度を足す
            if (vy < 0)
                return (2 * Math.PI) + n;  // 360度を足す
            return n;
        }

        private double ToDegree(double rad)
        {
            return rad * 360 / (2 * Math.PI);
        }

        private double ToRadian(double deg)
        {
            return deg * (2 * Math.PI) / 360;
        }

        private double LineLength(Point p1, Point p2)
        {
            var w = (p2.X - p1.X);
            var h = (p2.Y - p1.Y);
            return Math.Sqrt(w * w + h * h);
        }

        // 状況変化を知らせるために購読者に通知する
        private void Publish(LineInfo state)
        {
            foreach (var observer in _observers)
            {
                observer.OnNext(state);
            }
        }

        private List<IObserver<LineInfo>> _observers = new List<IObserver<LineInfo>>();

        public IDisposable Subscribe(IObserver<LineInfo> observer)
        {
            _observers.Add(observer);
            return observer as IDisposable;
        }
    }
}

Drawer.cs

using NGraphics;
using System;
using System.Collections.Generic;
using System.Linq;

namespace DragonCurve
{
    class Drawer : IObserver<LineInfo>
    {
        private EasyCanvas canvas;
        private int generation;

        public Drawer(int w, int h, int generation)
        {
            canvas = new EasyCanvas(w, h);
            this.generation = generation;
        }

        public void OnCompleted()
        {
            canvas.Write($"DragonCurve{generation}.png");
        }

        public void OnError(Exception error)
        {
            throw new NotImplementedException();
        }

        public void OnNext(LineInfo value)
        {
            canvas.DrawLine(value.Start.X, value.Start.Y, value.End.X, value.End.Y, color: Color.FromRGB(90,90,90));
        }
    }
}

EasyCanvas.cs

using NGraphics;
using System;
using System.Collections.Generic;
using System.Linq;

namespace DragonCurve
{
    class EasyCanvas
    {
        private IImageCanvas canvas;

        static EasyCanvas()
        {
        }

        public EasyCanvas(int width, int height)
        {
            canvas = Platforms.Current.CreateImageCanvas(new Size(width, height), scale: 2);
        }

        public void SetPixel(int x, int y, NGraphics.SolidBrush brush)
        {
            canvas.FillRectangle(x, y, 1, 1, brush);
        }

        public void Write(string path)
        {
            canvas.GetImage().SaveAsPng(path);
        }

        internal void DrawLine(float x1, float y1, float x2, float y2, Color color)
        {
            canvas.DrawLine(x1, y1, x2, y2, color);
        }
    }
}

※ ソースコードは、GitHubで公開しています。

結果

generationが、5, 10, 15の時の結果を示します。

generation: 5

DragonCurve5.png

generation: 10

DragonCurve10.png

generation: 15

DragonCurve15.png


この記事は、Gushwell's C# Programming Pageで公開したものを大幅に変更・加筆したものです。

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした