Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
1
Help us understand the problem. What is going on with this article?
@gushwell

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

More than 1 year has passed since last update.

はじめに

フラクタル図形の一種であるヘイウェイ・ドラゴン曲線と言われる図を描くプログラムを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で公開したものを大幅に変更・加筆したものです。

1
Help us understand the problem. What is going on with this article?
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
gushwell
株式会社ジード / Microsoft MVP for Developer Technologies 2005-2020 / 著書『実戦で役立つ C#プログラミングのイディオム/定石&パターン』『新・標準プログラマーズライブラリ なるほどなっとく C#入門』『C#プログラミング入門―オブジェクト指向のプログラミング手法を基礎から解説』

Comments

No comments
Sign up for free and join this conversation.
Sign Up
If you already have a Qiita account Login
1
Help us understand the problem. What is going on with this article?