|
| 1 | +packageOthers; |
| 2 | + |
| 3 | +importjava.awt.*; |
| 4 | +importjava.awt.image.BufferedImage; |
| 5 | +importjava.io.File; |
| 6 | +importjava.io.IOException; |
| 7 | +importjava.util.ArrayList; |
| 8 | +importjavax.imageio.ImageIO; |
| 9 | + |
| 10 | +/** |
| 11 | + * The Koch snowflake is a fractal curve and one of the earliest fractals to have been described. |
| 12 | + * The Koch snowflake can be built up iteratively, in a sequence of stages. The first stage is an |
| 13 | + * equilateral triangle, and each successive stage is formed by adding outward bends to each side of |
| 14 | + * the previous stage, making smaller equilateral triangles. This can be achieved through the |
| 15 | + * following steps for each line: 1. divide the line segment into three segments of equal length. 2. |
| 16 | + * draw an equilateral triangle that has the middle segment from step 1 as its base and points |
| 17 | + * outward. 3. remove the line segment that is the base of the triangle from step 2. (description |
| 18 | + * adapted from https://en.wikipedia.org/wiki/Koch_snowflake ) (for a more detailed explanation and |
| 19 | + * an implementation in the Processing language, see |
| 20 | + * https://natureofcode.com/book/chapter-8-fractals/ #84-the-koch-curve-and-the-arraylist-technique |
| 21 | + * ). |
| 22 | + */ |
| 23 | +publicclassKochSnowflake { |
| 24 | + |
| 25 | +publicstaticvoidmain(String[]args) { |
| 26 | +// Test Iterate-method |
| 27 | +ArrayList<Vector2>vectors =newArrayList<Vector2>(); |
| 28 | +vectors.add(newVector2(0,0)); |
| 29 | +vectors.add(newVector2(1,0)); |
| 30 | +ArrayList<Vector2>result =Iterate(vectors,1); |
| 31 | + |
| 32 | +assertresult.get(0).x ==0; |
| 33 | +assertresult.get(0).y ==0; |
| 34 | + |
| 35 | +assertresult.get(1).x ==1. /3; |
| 36 | +assertresult.get(1).y ==0; |
| 37 | + |
| 38 | +assertresult.get(2).x ==1. /2; |
| 39 | +assertresult.get(2).y ==Math.sin(Math.PI /3) /3; |
| 40 | + |
| 41 | +assertresult.get(3).x ==2. /3; |
| 42 | +assertresult.get(3).y ==0; |
| 43 | + |
| 44 | +assertresult.get(4).x ==1; |
| 45 | +assertresult.get(4).y ==0; |
| 46 | + |
| 47 | +// Test GetKochSnowflake-method |
| 48 | +intimageWidth =600; |
| 49 | +doubleoffsetX =imageWidth /10.; |
| 50 | +doubleoffsetY =imageWidth /3.7; |
| 51 | +BufferedImageimage =GetKochSnowflake(imageWidth,5); |
| 52 | + |
| 53 | +// The background should be white |
| 54 | +assertimage.getRGB(0,0) ==newColor(255,255,255).getRGB(); |
| 55 | + |
| 56 | +// The snowflake is drawn in black and this is the position of the first vector |
| 57 | +assertimage.getRGB((int)offsetX, (int)offsetY) ==newColor(0,0,0).getRGB(); |
| 58 | + |
| 59 | +// Save image |
| 60 | +try { |
| 61 | +ImageIO.write(image,"png",newFile("KochSnowflake.png")); |
| 62 | + }catch (IOExceptione) { |
| 63 | +e.printStackTrace(); |
| 64 | + } |
| 65 | + } |
| 66 | + |
| 67 | +/** |
| 68 | + * Go through the number of iterations determined by the argument "steps". Be careful with high |
| 69 | + * values (above 5) since the time to calculate increases exponentially. |
| 70 | + * |
| 71 | + * @param initialVectors The vectors composing the shape to which the algorithm is applied. |
| 72 | + * @param steps The number of iterations. |
| 73 | + * @return The transformed vectors after the iteration-steps. |
| 74 | + */ |
| 75 | +publicstaticArrayList<Vector2>Iterate(ArrayList<Vector2>initialVectors,intsteps) { |
| 76 | +ArrayList<Vector2>vectors =initialVectors; |
| 77 | +for (inti =0;i <steps;i++) { |
| 78 | +vectors =IterationStep(vectors); |
| 79 | + } |
| 80 | + |
| 81 | +returnvectors; |
| 82 | + } |
| 83 | + |
| 84 | +/** |
| 85 | + * Method to render the Koch snowflake to a image. |
| 86 | + * |
| 87 | + * @param imageWidth The width of the rendered image. |
| 88 | + * @param steps The number of iterations. |
| 89 | + * @return The image of the rendered Koch snowflake. |
| 90 | + */ |
| 91 | +publicstaticBufferedImageGetKochSnowflake(intimageWidth,intsteps) { |
| 92 | +if (imageWidth <=0) { |
| 93 | +thrownewIllegalArgumentException("imageWidth should be greater than zero"); |
| 94 | + } |
| 95 | + |
| 96 | +doubleoffsetX =imageWidth /10.; |
| 97 | +doubleoffsetY =imageWidth /3.7; |
| 98 | +Vector2vector1 =newVector2(offsetX,offsetY); |
| 99 | +Vector2vector2 = |
| 100 | +newVector2(imageWidth /2,Math.sin(Math.PI /3) *imageWidth *0.8 +offsetY); |
| 101 | +Vector2vector3 =newVector2(imageWidth -offsetX,offsetY); |
| 102 | +ArrayList<Vector2>initialVectors =newArrayList<Vector2>(); |
| 103 | +initialVectors.add(vector1); |
| 104 | +initialVectors.add(vector2); |
| 105 | +initialVectors.add(vector3); |
| 106 | +initialVectors.add(vector1); |
| 107 | +ArrayList<Vector2>vectors =Iterate(initialVectors,steps); |
| 108 | +returnGetImage(vectors,imageWidth,imageWidth); |
| 109 | + } |
| 110 | + |
| 111 | +/** |
| 112 | + * Loops through each pair of adjacent vectors. Each line between two adjacent vectors is divided |
| 113 | + * into 4 segments by adding 3 additional vectors in-between the original two vectors. The vector |
| 114 | + * in the middle is constructed through a 60 degree rotation so it is bent outwards. |
| 115 | + * |
| 116 | + * @param vectors The vectors composing the shape to which the algorithm is applied. |
| 117 | + * @return The transformed vectors after the iteration-step. |
| 118 | + */ |
| 119 | +privatestaticArrayList<Vector2>IterationStep(ArrayList<Vector2>vectors) { |
| 120 | +ArrayList<Vector2>newVectors =newArrayList<Vector2>(); |
| 121 | +for (inti =0;i <vectors.size() -1;i++) { |
| 122 | +Vector2startVector =vectors.get(i); |
| 123 | +Vector2endVector =vectors.get(i +1); |
| 124 | +newVectors.add(startVector); |
| 125 | +Vector2differenceVector =endVector.subtract(startVector).multiply(1. /3); |
| 126 | +newVectors.add(startVector.add(differenceVector)); |
| 127 | +newVectors.add(startVector.add(differenceVector).add(differenceVector.rotate(60))); |
| 128 | +newVectors.add(startVector.add(differenceVector.multiply(2))); |
| 129 | + } |
| 130 | + |
| 131 | +newVectors.add(vectors.get(vectors.size() -1)); |
| 132 | +returnnewVectors; |
| 133 | + } |
| 134 | + |
| 135 | +/** |
| 136 | + * Utility-method to render the Koch snowflake to an image. |
| 137 | + * |
| 138 | + * @param vectors The vectors defining the edges to be rendered. |
| 139 | + * @param imageWidth The width of the rendered image. |
| 140 | + * @param imageHeight The height of the rendered image. |
| 141 | + * @return The image of the rendered edges. |
| 142 | + */ |
| 143 | +privatestaticBufferedImageGetImage( |
| 144 | +ArrayList<Vector2>vectors,intimageWidth,intimageHeight) { |
| 145 | +BufferedImageimage =newBufferedImage(imageWidth,imageHeight,BufferedImage.TYPE_INT_RGB); |
| 146 | +Graphics2Dg2d =image.createGraphics(); |
| 147 | + |
| 148 | +// Set the background white |
| 149 | +g2d.setBackground(Color.WHITE); |
| 150 | +g2d.fillRect(0,0,imageWidth,imageHeight); |
| 151 | + |
| 152 | +// Draw the edges |
| 153 | +g2d.setColor(Color.BLACK); |
| 154 | +BasicStrokebs =newBasicStroke(1); |
| 155 | +g2d.setStroke(bs); |
| 156 | +for (inti =0;i <vectors.size() -1;i++) { |
| 157 | +intx1 = (int)vectors.get(i).x; |
| 158 | +inty1 = (int)vectors.get(i).y; |
| 159 | +intx2 = (int)vectors.get(i +1).x; |
| 160 | +inty2 = (int)vectors.get(i +1).y; |
| 161 | + |
| 162 | +g2d.drawLine(x1,y1,x2,y2); |
| 163 | + } |
| 164 | + |
| 165 | +returnimage; |
| 166 | + } |
| 167 | + |
| 168 | +/** Inner class to handle the vector calculations. */ |
| 169 | +privatestaticclassVector2 { |
| 170 | + |
| 171 | +doublex,y; |
| 172 | + |
| 173 | +publicVector2(doublex,doubley) { |
| 174 | +this.x =x; |
| 175 | +this.y =y; |
| 176 | + } |
| 177 | + |
| 178 | +@Override |
| 179 | +publicStringtoString() { |
| 180 | +returnString.format("[%f, %f]",this.x,this.y); |
| 181 | + } |
| 182 | + |
| 183 | +/** |
| 184 | + * Vector addition |
| 185 | + * |
| 186 | + * @param vector The vector to be added. |
| 187 | + * @return The sum-vector. |
| 188 | + */ |
| 189 | +publicVector2add(Vector2vector) { |
| 190 | +doublex =this.x +vector.x; |
| 191 | +doubley =this.y +vector.y; |
| 192 | +returnnewVector2(x,y); |
| 193 | + } |
| 194 | + |
| 195 | +/** |
| 196 | + * Vector subtraction |
| 197 | + * |
| 198 | + * @param vector The vector to be subtracted. |
| 199 | + * @return The difference-vector. |
| 200 | + */ |
| 201 | +publicVector2subtract(Vector2vector) { |
| 202 | +doublex =this.x -vector.x; |
| 203 | +doubley =this.y -vector.y; |
| 204 | +returnnewVector2(x,y); |
| 205 | + } |
| 206 | + |
| 207 | +/** |
| 208 | + * Vector scalar multiplication |
| 209 | + * |
| 210 | + * @param scalar The factor by which to multiply the vector. |
| 211 | + * @return The scaled vector. |
| 212 | + */ |
| 213 | +publicVector2multiply(doublescalar) { |
| 214 | +doublex =this.x *scalar; |
| 215 | +doubley =this.y *scalar; |
| 216 | +returnnewVector2(x,y); |
| 217 | + } |
| 218 | + |
| 219 | +/** |
| 220 | + * Vector rotation (see https://en.wikipedia.org/wiki/Rotation_matrix) |
| 221 | + * |
| 222 | + * @param angleInDegrees The angle by which to rotate the vector. |
| 223 | + * @return The rotated vector. |
| 224 | + */ |
| 225 | +publicVector2rotate(doubleangleInDegrees) { |
| 226 | +doubleradians =angleInDegrees *Math.PI /180; |
| 227 | +doubleca =Math.cos(radians); |
| 228 | +doublesa =Math.sin(radians); |
| 229 | +doublex =ca *this.x -sa *this.y; |
| 230 | +doubley =sa *this.x +ca *this.y; |
| 231 | +returnnewVector2(x,y); |
| 232 | + } |
| 233 | + } |
| 234 | +} |