From a99321502cd9b004afee004ade75e732e1239b8f Mon Sep 17 00:00:00 2001 From: algobytewise Date: Sat, 24 Apr 2021 12:03:24 +0530 Subject: [PATCH] add Koch snowflake (#2168) * add KochSnowflake.java * add package --- Others/KochSnowflake.java | 234 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 234 insertions(+) create mode 100644 Others/KochSnowflake.java diff --git a/Others/KochSnowflake.java b/Others/KochSnowflake.java new file mode 100644 index 000000000..3ec76ae1b --- /dev/null +++ b/Others/KochSnowflake.java @@ -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 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); + } + } +}