Skip to content

Commit 7c12bfe

Browse files
algobytewisesiriak
andauthored
Add koch snowflake (#214)
Co-authored-by: Andrii Siriak <siryaka@gmail.com>
1 parent 4cdde81 commit 7c12bfe

File tree

3 files changed

+199
-0
lines changed

3 files changed

+199
-0
lines changed
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
using System;
2+
using System.Numerics;
3+
using System.Collections.Generic;
4+
using System.Drawing;
5+
using NUnit.Framework;
6+
using FluentAssertions;
7+
8+
namespace Algorithms.Tests.Other
9+
{
10+
public static class KochSnowflakeTest
11+
{
12+
[Test]
13+
public static void TestIterateMethod()
14+
{
15+
List<Vector2> vectors = new List<Vector2> {new Vector2(0, 0), new Vector2(1, 0)};
16+
List<Vector2> result = Algorithms.Other.KochSnowflake.Iterate(vectors, steps: 1);
17+
result[0].Should().Be(new Vector2(0, 0));
18+
result[1].Should().Be(new Vector2((float) 1 / 3, 0));
19+
20+
/* Should().BeApproximately() is not defined for Vector2 or float
21+
so the x-y-components have to be tested separately and the y-component needs to be cast to double */
22+
result[2].X.Should().Be(0.5f);
23+
((double)result[2].Y).Should().BeApproximately(Math.Sin(Math.PI / 3) / 3, 0.0001);
24+
25+
result[3].Should().Be(new Vector2((float) 2 / 3, 0));
26+
result[4].Should().Be(new Vector2(1, 0));
27+
}
28+
29+
[Test]
30+
public static void BitmapWidthIsZeroOrNegative_ThrowsArgumentOutOfRangeException()
31+
{
32+
Assert.Throws<ArgumentOutOfRangeException>(() => Algorithms.Other.KochSnowflake.GetKochSnowflake(bitmapWidth:-200));
33+
}
34+
35+
[Test]
36+
public static void TestKochSnowflakeExample()
37+
{
38+
int bitmapWidth = 600;
39+
float offsetX = bitmapWidth / 10f;
40+
float offsetY = bitmapWidth / 3.7f;
41+
42+
Bitmap bitmap = Algorithms.Other.KochSnowflake.GetKochSnowflake(bitmapWidth: 600);
43+
bitmap.GetPixel(0, 0).Should().Be(Color.FromArgb(255, 255, 255, 255), "because the background should be white");
44+
bitmap.GetPixel((int)offsetX, (int)offsetY).Should().Be(Color.FromArgb(255, 0, 0, 0), "because the snowflake is drawn in black and this is the position of the first vector");
45+
}
46+
}
47+
}

Algorithms/Other/KochSnowflake.cs

Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Drawing;
4+
using System.Numerics;
5+
6+
namespace Algorithms.Other
7+
{
8+
/// <summary>
9+
/// The Koch snowflake is a fractal curve and one of the earliest fractals to
10+
/// have been described. The Koch snowflake can be built up iteratively, in a
11+
/// sequence of stages. The first stage is an equilateral triangle, and each
12+
/// successive stage is formed by adding outward bends to each side of the
13+
/// previous stage, making smaller equilateral triangles.
14+
/// This can be achieved through the following steps for each line:
15+
/// 1. divide the line segment into three segments of equal length.
16+
/// 2. draw an equilateral triangle that has the middle segment from step 1
17+
/// as its base and points outward.
18+
/// 3. remove the line segment that is the base of the triangle from step 2.
19+
/// (description adapted from https://en.wikipedia.org/wiki/Koch_snowflake )
20+
/// (for a more detailed explanation and an implementation in the
21+
/// Processing language, see https://natureofcode.com/book/chapter-8-fractals/
22+
/// #84-the-koch-curve-and-the-arraylist-technique ).
23+
/// </summary>
24+
public static class KochSnowflake
25+
{
26+
/// <summary>
27+
/// Go through the number of iterations determined by the argument "steps".
28+
/// Be careful with high values (above 5) since the time to calculate increases
29+
/// exponentially.
30+
/// </summary>
31+
/// <param name="initialVectors">The vectors composing the shape to which
32+
/// the algorithm is applied.</param>
33+
/// <param name="steps">The number of iterations.</param>
34+
/// <returns>The transformed vectors after the iteration-steps.</returns>
35+
public static List<Vector2> Iterate(List<Vector2> initialVectors, int steps = 5)
36+
{
37+
List<Vector2> vectors = initialVectors;
38+
for (int i = 0; i < steps; i++)
39+
{
40+
vectors = IterationStep(vectors);
41+
}
42+
43+
return vectors;
44+
}
45+
46+
/// <summary>
47+
/// Method to render the Koch snowflake to a bitmap. To save the
48+
/// bitmap the command 'GetKochSnowflake().Save("KochSnowflake.png")' can be used.
49+
/// </summary>
50+
/// <param name="bitmapWidth">The width of the rendered bitmap.</param>
51+
/// <param name="steps">The number of iterations.</param>
52+
/// <returns>The bitmap of the rendered Koch snowflake.</returns>
53+
public static Bitmap GetKochSnowflake(
54+
int bitmapWidth = 600,
55+
int steps = 5)
56+
{
57+
if (bitmapWidth <= 0)
58+
{
59+
throw new ArgumentOutOfRangeException(nameof(bitmapWidth), $"{nameof(bitmapWidth)} should be greater than zero");
60+
}
61+
62+
float offsetX = bitmapWidth / 10f;
63+
float offsetY = bitmapWidth / 3.7f;
64+
Vector2 vector1 = new Vector2(offsetX, offsetY);
65+
Vector2 vector2 = new Vector2(bitmapWidth / 2, (float)Math.Sin(Math.PI / 3) * bitmapWidth * 0.8f + offsetY);
66+
Vector2 vector3 = new Vector2(bitmapWidth - offsetX, offsetY);
67+
List<Vector2> initialVectors = new List<Vector2> { vector1, vector2, vector3, vector1 };
68+
List<Vector2> vectors = Iterate(initialVectors, steps);
69+
return GetBitmap(vectors, bitmapWidth, bitmapWidth);
70+
}
71+
72+
/// <summary>
73+
/// Loops through each pair of adjacent vectors. Each line between two adjacent
74+
/// vectors is divided into 4 segments by adding 3 additional vectors in-between
75+
/// the original two vectors. The vector in the middle is constructed through a
76+
/// 60 degree rotation so it is bent outwards.
77+
/// </summary>
78+
/// <param name="vectors">The vectors composing the shape to which
79+
/// the algorithm is applied.</param>
80+
/// <returns>The transformed vectors after the iteration-step.</returns>
81+
private static List<Vector2> IterationStep(List<Vector2> vectors)
82+
{
83+
List<Vector2> newVectors = new List<Vector2>();
84+
for (int i = 0; i < vectors.Count - 1; i++)
85+
{
86+
Vector2 startVector = vectors[i];
87+
Vector2 endVector = vectors[i + 1];
88+
newVectors.Add(startVector);
89+
Vector2 differenceVector = endVector - startVector;
90+
newVectors.Add(startVector + differenceVector / 3);
91+
newVectors.Add(startVector + differenceVector / 3 + Rotate(differenceVector / 3, 60));
92+
newVectors.Add(startVector + differenceVector * 2 / 3);
93+
}
94+
95+
newVectors.Add(vectors[vectors.Count - 1]);
96+
return newVectors;
97+
}
98+
99+
/// <summary>
100+
/// Standard rotation of a 2D vector with a rotation matrix
101+
/// (see https://en.wikipedia.org/wiki/Rotation_matrix ).
102+
/// </summary>
103+
/// <param name="vector">The vector to be rotated.</param>
104+
/// <param name="angleInDegrees">The angle by which to rotate the vector.</param>
105+
/// <returns>The rotated vector.</returns>
106+
private static Vector2 Rotate(Vector2 vector, float angleInDegrees)
107+
{
108+
float radians = angleInDegrees * (float)Math.PI / 180;
109+
float ca = (float)Math.Cos(radians);
110+
float sa = (float)Math.Sin(radians);
111+
return new Vector2(ca * vector.X - sa * vector.Y, sa * vector.X + ca * vector.Y);
112+
}
113+
114+
/// <summary>
115+
/// Utility-method to render the Koch snowflake to a bitmap.
116+
/// </summary>
117+
/// <param name="vectors">The vectors defining the edges to be rendered.</param>
118+
/// <param name="bitmapWidth">The width of the rendered bitmap.</param>
119+
/// <param name="bitmapHeight">The height of the rendered bitmap.</param>
120+
/// <returns>The bitmap of the rendered edges.</returns>
121+
private static Bitmap GetBitmap(
122+
List<Vector2> vectors,
123+
int bitmapWidth,
124+
int bitmapHeight)
125+
{
126+
Bitmap bitmap = new Bitmap(bitmapWidth, bitmapHeight);
127+
128+
using (Graphics graphics = Graphics.FromImage(bitmap))
129+
{
130+
// Set the background white
131+
Rectangle imageSize = new Rectangle(0, 0, bitmapWidth, bitmapHeight);
132+
graphics.FillRectangle(Brushes.White, imageSize);
133+
134+
// Draw the edges
135+
for (int i = 0; i < vectors.Count - 1; i++)
136+
{
137+
Pen blackPen = new Pen(Color.Black, 1);
138+
139+
float x1 = vectors[i].X;
140+
float y1 = vectors[i].Y;
141+
float x2 = vectors[i + 1].X;
142+
float y2 = vectors[i + 1].Y;
143+
144+
graphics.DrawLine(blackPen, x1, y1, x2, y2);
145+
}
146+
}
147+
148+
return bitmap;
149+
}
150+
}
151+
}

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@ This repository contains algorithms and data structures implemented in C# for ed
9494
* [Sieve of Eratosthenes](./Algorithms/Other/SieveOfEratosthenes.cs)
9595
* [Luhn](./Algorithms/Other/Luhn.cs)
9696
* [Mandelbrot](./Algorithms/Other/Mandelbrot.cs)
97+
* [Koch Snowflake](./Algorithms/Other/KochSnowflake.cs)
9798
* [Problems](./Algorithms/Problems/)
9899
* [Stable Marriage](./Algorithms/Problems/StableMarriage)
99100
* [Gale-Shapley](./Algorithms/Problems/StableMarriage/GaleShapley.cs)

0 commit comments

Comments
 (0)