JavaAlgorithms/Others/KochSnowflake.java

235 lines
7.9 KiB
Java
Raw Normal View History

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);
}
}
}