235 lines
7.9 KiB
Java
235 lines
7.9 KiB
Java
|
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);
|
||
|
}
|
||
|
}
|
||
|
}
|