Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
234 changes: 234 additions & 0 deletions Others/KochSnowflake.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,234 @@
package Others;

import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import javax.imageio.ImageIO;

/**
* The Koch snowflake is a fractal curve and one of the earliest fractals to have been described.
* The Koch snowflake can be built up iteratively, in a sequence of stages. The first stage is an
* equilateral triangle, and each successive stage is formed by adding outward bends to each side of
* the previous stage, making smaller equilateral triangles. This can be achieved through the
* following steps for each line: 1. divide the line segment into three segments of equal length. 2.
* draw an equilateral triangle that has the middle segment from step 1 as its base and points
* outward. 3. remove the line segment that is the base of the triangle from step 2. (description
* adapted from https://en.wikipedia.org/wiki/Koch_snowflake ) (for a more detailed explanation and
* an implementation in the Processing language, see
* https://natureofcode.com/book/chapter-8-fractals/ #84-the-koch-curve-and-the-arraylist-technique
* ).
*/
public class KochSnowflake {

public static void main(String[] args) {
// Test Iterate-method
ArrayList<Vector2> vectors = new ArrayList<Vector2>();
vectors.add(new Vector2(0, 0));
vectors.add(new Vector2(1, 0));
ArrayList<Vector2> result = Iterate(vectors, 1);

assert result.get(0).x == 0;
assert result.get(0).y == 0;

assert result.get(1).x == 1. / 3;
assert result.get(1).y == 0;

assert result.get(2).x == 1. / 2;
assert result.get(2).y == Math.sin(Math.PI / 3) / 3;

assert result.get(3).x == 2. / 3;
assert result.get(3).y == 0;

assert result.get(4).x == 1;
assert result.get(4).y == 0;

// Test GetKochSnowflake-method
int imageWidth = 600;
double offsetX = imageWidth / 10.;
double offsetY = imageWidth / 3.7;
BufferedImage image = GetKochSnowflake(imageWidth, 5);

// The background should be white
assert image.getRGB(0, 0) == new Color(255, 255, 255).getRGB();

// The snowflake is drawn in black and this is the position of the first vector
assert image.getRGB((int) offsetX, (int) offsetY) == new Color(0, 0, 0).getRGB();

// Save image
try {
ImageIO.write(image, "png", new File("KochSnowflake.png"));
} catch (IOException e) {
e.printStackTrace();
}
}

/**
* Go through the number of iterations determined by the argument "steps". Be careful with high
* values (above 5) since the time to calculate increases exponentially.
*
* @param initialVectors The vectors composing the shape to which the algorithm is applied.
* @param steps The number of iterations.
* @return The transformed vectors after the iteration-steps.
*/
public static ArrayList<Vector2> Iterate(ArrayList<Vector2> initialVectors, int steps) {
ArrayList<Vector2> vectors = initialVectors;
for (int i = 0; i < steps; i++) {
vectors = IterationStep(vectors);
}

return vectors;
}

/**
* Method to render the Koch snowflake to a image.
*
* @param imageWidth The width of the rendered image.
* @param steps The number of iterations.
* @return The image of the rendered Koch snowflake.
*/
public static BufferedImage GetKochSnowflake(int imageWidth, int steps) {
if (imageWidth <= 0) {
throw new IllegalArgumentException("imageWidth should be greater than zero");
}

double offsetX = imageWidth / 10.;
double offsetY = imageWidth / 3.7;
Vector2 vector1 = new Vector2(offsetX, offsetY);
Vector2 vector2 =
new Vector2(imageWidth / 2, Math.sin(Math.PI / 3) * imageWidth * 0.8 + offsetY);
Vector2 vector3 = new Vector2(imageWidth - offsetX, offsetY);
ArrayList<Vector2> initialVectors = new ArrayList<Vector2>();
initialVectors.add(vector1);
initialVectors.add(vector2);
initialVectors.add(vector3);
initialVectors.add(vector1);
ArrayList<Vector2> vectors = Iterate(initialVectors, steps);
return GetImage(vectors, imageWidth, imageWidth);
}

/**
* Loops through each pair of adjacent vectors. Each line between two adjacent vectors is divided
* into 4 segments by adding 3 additional vectors in-between the original two vectors. The vector
* in the middle is constructed through a 60 degree rotation so it is bent outwards.
*
* @param vectors The vectors composing the shape to which the algorithm is applied.
* @return The transformed vectors after the iteration-step.
*/
private static ArrayList<Vector2> IterationStep(ArrayList<Vector2> vectors) {
ArrayList<Vector2> newVectors = new ArrayList<Vector2>();
for (int i = 0; i < vectors.size() - 1; i++) {
Vector2 startVector = vectors.get(i);
Vector2 endVector = vectors.get(i + 1);
newVectors.add(startVector);
Vector2 differenceVector = endVector.subtract(startVector).multiply(1. / 3);
newVectors.add(startVector.add(differenceVector));
newVectors.add(startVector.add(differenceVector).add(differenceVector.rotate(60)));
newVectors.add(startVector.add(differenceVector.multiply(2)));
}

newVectors.add(vectors.get(vectors.size() - 1));
return newVectors;
}

/**
* Utility-method to render the Koch snowflake to an image.
*
* @param vectors The vectors defining the edges to be rendered.
* @param imageWidth The width of the rendered image.
* @param imageHeight The height of the rendered image.
* @return The image of the rendered edges.
*/
private static BufferedImage GetImage(
ArrayList<Vector2> vectors, int imageWidth, int imageHeight) {
BufferedImage image = new BufferedImage(imageWidth, imageHeight, BufferedImage.TYPE_INT_RGB);
Graphics2D g2d = image.createGraphics();

// Set the background white
g2d.setBackground(Color.WHITE);
g2d.fillRect(0, 0, imageWidth, imageHeight);

// Draw the edges
g2d.setColor(Color.BLACK);
BasicStroke bs = new BasicStroke(1);
g2d.setStroke(bs);
for (int i = 0; i < vectors.size() - 1; i++) {
int x1 = (int) vectors.get(i).x;
int y1 = (int) vectors.get(i).y;
int x2 = (int) vectors.get(i + 1).x;
int y2 = (int) vectors.get(i + 1).y;

g2d.drawLine(x1, y1, x2, y2);
}

return image;
}

/** Inner class to handle the vector calculations. */
private static class Vector2 {

double x, y;

public Vector2(double x, double y) {
this.x = x;
this.y = y;
}

@Override
public String toString() {
return String.format("[%f, %f]", this.x, this.y);
}

/**
* Vector addition
*
* @param vector The vector to be added.
* @return The sum-vector.
*/
public Vector2 add(Vector2 vector) {
double x = this.x + vector.x;
double y = this.y + vector.y;
return new Vector2(x, y);
}

/**
* Vector subtraction
*
* @param vector The vector to be subtracted.
* @return The difference-vector.
*/
public Vector2 subtract(Vector2 vector) {
double x = this.x - vector.x;
double y = this.y - vector.y;
return new Vector2(x, y);
}

/**
* Vector scalar multiplication
*
* @param scalar The factor by which to multiply the vector.
* @return The scaled vector.
*/
public Vector2 multiply(double scalar) {
double x = this.x * scalar;
double y = this.y * scalar;
return new Vector2(x, y);
}

/**
* Vector rotation (see https://en.wikipedia.org/wiki/Rotation_matrix)
*
* @param angleInDegrees The angle by which to rotate the vector.
* @return The rotated vector.
*/
public Vector2 rotate(double angleInDegrees) {
double radians = angleInDegrees * Math.PI / 180;
double ca = Math.cos(radians);
double sa = Math.sin(radians);
double x = ca * this.x - sa * this.y;
double y = sa * this.x + ca * this.y;
return new Vector2(x, y);
}
}
}