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 vectors = new ArrayList(); vectors.add(new Vector2(0, 0)); vectors.add(new Vector2(1, 0)); ArrayList 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 Iterate(ArrayList initialVectors, int steps) { ArrayList 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 initialVectors = new ArrayList(); initialVectors.add(vector1); initialVectors.add(vector2); initialVectors.add(vector3); initialVectors.add(vector1); ArrayList 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 IterationStep(ArrayList vectors) { ArrayList newVectors = new ArrayList(); 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 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); } } }