diff --git a/.idea/.gitignore b/.idea/.gitignore index 13566b81b018ad684f3a35fee301741b2734c8f4..a9d7db9c0a81b2db47ca92e4e180b30090b27632 100644 --- a/.idea/.gitignore +++ b/.idea/.gitignore @@ -6,3 +6,5 @@ # Datasource local storage ignored files /dataSources/ /dataSources.local.xml +# GitHub Copilot persisted chat sessions +/copilot/chatSessions diff --git a/src/main/java/org/example/chaosgame/Main.java b/src/main/java/org/example/chaosgame/Main.java index 7333f1bc4e3385849d668ecd39a246c14223d73d..2427244cb78cb808339124d55904e3edbdc3407c 100644 --- a/src/main/java/org/example/chaosgame/Main.java +++ b/src/main/java/org/example/chaosgame/Main.java @@ -21,20 +21,18 @@ import org.example.chaosgame.linalg.Matrix2x2; import java.util.List; +import org.example.chaosgame.chaos.ChaosGameFileHandler; + public class Main extends Application { @Override public void start(Stage primaryStage) throws Exception { - // Create ChaosGameDescription and ChaosGame - AffineTransform2D transform = new AffineTransform2D( - new Matrix2x2(0.5, 0, 0, 0.5), - new Vector2D(0, 0)); - AffineTransform2D transform2 = new AffineTransform2D( - new Matrix2x2(0.5, 0, 0, 0.5), - new Vector2D(0.5, 0)); - AffineTransform2D transform3 = new AffineTransform2D( - new Matrix2x2(0.5, 0, 0, 0.5), - new Vector2D(0.25, 0.5)); - List<Transform2D> transforms = List.of(transform, transform2, transform3); + ChaosGameFileHandler fileHandler = new ChaosGameFileHandler(); +// try { +// // Change this to the path of the file you want to read +// description = fileHandler.readFromFile("src/main/resources/barnsley.txt"); +// } catch (Exception e) { +// System.err.println(e);; +// } JuliaTransform juliaTransform = new JuliaTransform( new Complex(-0.70176, -0.3842), 1); @@ -46,15 +44,20 @@ public class Main extends Application { ExploreJulia exploreTransform = new ExploreJulia(point); List<Transform2D> juliaTransforms = List.of(exploreTransform); + ChaosGameDescription description = new ChaosGameDescription(new Vector2D(-1.6, -1), + new Vector2D(1.6, 1), juliaTransforms); + - ChaosGameDescription description = new ChaosGameDescription( - new Vector2D(-1.6, -1), - new Vector2D(1.6, 1), - juliaTransforms - ); ExploreGame exploreGame = new ExploreGame(description, 1200, 800); - ChaosGame game = new ChaosGame(description, 1200, 800); exploreGame.exploreFractals(); + + if (description == null) { + System.out.println("Failed to read file"); + return; + } + +// ChaosGame game = new ChaosGame(description, 1200, 800); +// game.runStepsBarnsley(1000000); ChaosCanvas chaosCanvas = exploreGame.getCanvas(); // Create a JavaFX canvas @@ -69,10 +72,13 @@ public class Main extends Application { for (int i = 0; i < chaosCanvas.getHeight(); i++) { for (int j = 0; j < chaosCanvas.getWidth(); j++) { - int scaledValue = Math.min(255, canvasArray[i][j] * 3); // Scale up, but don't exceed 255 - gc.setFill(Color.rgb(scaledValue, 0, 0, 1)); -// gc.setFill(Color.rgb(canvasArray[i][j], 0, 0, 1)); - + int color = canvasArray[i][j]; + if (color == 0) { + gc.setFill(Color.BLACK); + } else { + //hue based on the value of the pixel + gc.setFill(Color.hsb(color, 1.0, 1.0)); + } gc.fillRect(j * cellWidth, i * cellHeight, cellWidth, cellHeight); } } diff --git a/src/main/java/org/example/chaosgame/chaos/ChaosCanvas.java b/src/main/java/org/example/chaosgame/chaos/ChaosCanvas.java index da6929e832dcbb5267c6b402d121c58a8d9f1a35..626e9a17e8eb1a2ec9c4c57aa668dba357daab5c 100644 --- a/src/main/java/org/example/chaosgame/chaos/ChaosCanvas.java +++ b/src/main/java/org/example/chaosgame/chaos/ChaosCanvas.java @@ -4,6 +4,16 @@ import org.example.chaosgame.linalg.Matrix2x2; import org.example.chaosgame.linalg.Vector2D; import org.example.chaosgame.transformations.AffineTransform2D; +/** + * A class representing a canvas for the chaos game. + * The canvas is a 2D grid of pixels, where each pixel is represented by an integer value. + * The canvas has a coordinate system, where the origin is in the lower left corner of the canvas, + * and the x-axis and y-axis are horizontal and vertical, respectively. + * The canvas has a minimum and maximum coordinate, which defines the extent of the canvas. + * The canvas also has an Affine transformation that maps coordinates, {@link Vector2D}, + * to indices in the canvas array. This is used to map points to pixels in the canvas. + */ + public class ChaosCanvas { private final int width; private final int height; @@ -12,6 +22,21 @@ public class ChaosCanvas { private final Vector2D maxCoords; private final AffineTransform2D transformCoordsToIndices; + /** + * Creates a new ChaosCanvas with the given width, height, minimum and maximum coordinates. + * The canvas is initialized with all pixel values set to 0. + * The Affine transformation is calculated based on the width, height, + * minimum and maximum coordinates. + * + * @param width The width of the canvas + * + * @param height The height of the canvas + * + * @param minCoords The minimum coordinates of the canvas + * + * @param maxCoords The maximum coordinates of the canvas + * + */ public ChaosCanvas(int width, int height, Vector2D minCoords, @@ -21,14 +46,14 @@ public class ChaosCanvas { this.minCoords = minCoords; this.maxCoords = maxCoords; this.transformCoordsToIndices = new AffineTransform2D( - new Matrix2x2(0.0, ((height - 1) / (minCoords.getY() - maxCoords.getY())), - (width - 1) / (maxCoords.getX() - minCoords.getX()), 0.0), + new Matrix2x2(0.0, ((height - 1) + / (minCoords.getY() - maxCoords.getY())), + ((width - 1) / (maxCoords.getX() - minCoords.getX())), 0.0), new Vector2D((((height - 1.0) * maxCoords.getY()) / (maxCoords.getY() - minCoords.getY())), ((width - 1.0) * minCoords.getX()) / (minCoords.getX() - maxCoords.getX()) )); - //Vector2D a = transformCoordsToIndices.transform(new Vector2D(width, height)); this.canvas = new int[height][width]; } @@ -39,7 +64,14 @@ public class ChaosCanvas { return canvas[x][y]; } - public void putPixel (Vector2D point){ + + /** + * Increments the pixel value at the given point by 1. + * If the point is outside the canvas, the method does nothing. + * + * @param point The point to put a pixel at + */ + public void putPixel(Vector2D point) { Vector2D indices = transformCoordsToIndices.transform(point); int x = (int) indices.getX(); int y = (int) indices.getY(); @@ -59,7 +91,8 @@ public class ChaosCanvas { double y = (j * (minCoords.getY() - maxCoords.getY()) / (height - 1)) + maxCoords.getY(); return new Vector2D(x, y); } - public int[][] getCanvasArray(){ + + public int[][] getCanvasArray() { return canvas; } @@ -78,4 +111,12 @@ public class ChaosCanvas { public Vector2D getMaxCoords() { return maxCoords; } + + public void clearCanvas() { + for (int i = 0; i < height; i++) { + for (int j = 0; j < width; j++) { + canvas[i][j] = 0; + } + } + } } diff --git a/src/main/java/org/example/chaosgame/chaos/ChaosGame.java b/src/main/java/org/example/chaosgame/chaos/ChaosGame.java index 24c1292a1d0233579f459112f5f2a157eb283e89..817143d88f1652aa8538265a39ea5ecd883907d7 100644 --- a/src/main/java/org/example/chaosgame/chaos/ChaosGame.java +++ b/src/main/java/org/example/chaosgame/chaos/ChaosGame.java @@ -1,14 +1,17 @@ package org.example.chaosgame.chaos; -import javafx.scene.image.PixelWriter; -import javafx.scene.image.WritableImage; -import javafx.scene.paint.Color; -import org.example.chaosgame.linalg.Matrix2x2; -import org.example.chaosgame.linalg.Vector2D; -import org.example.chaosgame.transformations.AffineTransform2D; - import java.util.Random; +import org.example.chaosgame.linalg.Vector2D; +/** + * Class for running a chaos game. + * The chaos game is a method for generating fractals. + * The game is played by starting with a point and then randomly + * selecting a transformation from a set of transformations. + * The selected transformation is then applied to the current point. + * The new point is then drawn on the canvas. + * This process is repeated a selected amount of steps. + */ public class ChaosGame { private final ChaosCanvas canvas; @@ -18,9 +21,20 @@ public class ChaosGame { public final Random random = new Random(); + /** + * Constructor for ChaosGame. + * + * @param description Description of the chaos game + * + * @param width Width of the canvas + * + * @param height Height of the canvas + */ + public ChaosGame(ChaosGameDescription description, int width, int height) { this.description = description; - this.canvas = new ChaosCanvas(width, height, description.getMinCoords(), description.getMaxCoords()); + this.canvas = new ChaosCanvas(width, height, + description.getMinCoords(), description.getMaxCoords()); } @@ -28,14 +42,41 @@ public class ChaosGame { return canvas; } - public void runSteps (int steps){ - // canvas.putPixel(currentPoint); first point may need to be drawn - for (int i = 0; i < steps; i++){ + /** + * Method for running the chaos game. Randomly selects a transformation + * from the description and applies it to the current point. + * + * @param steps Number of steps to run + */ + public void runSteps(int steps) { + for (int i = 0; i < steps; i++) { int transformIndex = random.nextInt(description.getTransforms().size()); currentPoint = description.getTransforms().get(transformIndex).transform(currentPoint); canvas.putPixel(currentPoint); } } - + /** + * Method for running the Barnsley chaos game. Randomly selects a transformation + * from the description and applies it to the current point. + * The Barnsley chaos game has a different probability distribution + * for selecting transformations. + * + * @param steps Number of steps to run + */ + public void runStepsBarnsley(int steps) { + for (int i = 0; i < steps; i++) { + int test = random.nextInt(100); + if (test < 1) { + currentPoint = description.getTransforms().getFirst().transform(currentPoint); + } else if (test < 86) { + currentPoint = description.getTransforms().get(1).transform(currentPoint); + } else if (test < 93) { + currentPoint = description.getTransforms().get(2).transform(currentPoint); + } else { + currentPoint = description.getTransforms().get(3).transform(currentPoint); + } + canvas.putPixel(currentPoint); + } + } } diff --git a/src/main/java/org/example/chaosgame/chaos/ChaosGameDescription.java b/src/main/java/org/example/chaosgame/chaos/ChaosGameDescription.java index d8ef8fcde27fbbda56ad83c57e843147364eb355..b01e96cbe5ed8a182627a39df5e9af5b8faee57e 100644 --- a/src/main/java/org/example/chaosgame/chaos/ChaosGameDescription.java +++ b/src/main/java/org/example/chaosgame/chaos/ChaosGameDescription.java @@ -1,16 +1,31 @@ package org.example.chaosgame.chaos; +import java.util.List; import org.example.chaosgame.linalg.Vector2D; import org.example.chaosgame.transformations.Transform2D; -import java.util.List; +/** + * This class represents a chaos game description. + * It contains the minimum and maximum coordinates of the game area, + * and a list of transformations to apply to the points. + */ public class ChaosGameDescription { private final Vector2D minCoords; private final Vector2D maxCoords; private final List<Transform2D> transforms; - public ChaosGameDescription(Vector2D minCoords, Vector2D maxCoords, List<Transform2D> transforms) { + /** + * Constructor for ChaosGameDescription. + * + * @param minCoords Minimum coordinates of the game area + * + * @param maxCoords Maximum coordinates of the game area + * + * @param transforms List of transformations to apply to the points + */ + public ChaosGameDescription(Vector2D minCoords, Vector2D maxCoords, + List<Transform2D> transforms) { this.minCoords = minCoords; this.maxCoords = maxCoords; this.transforms = transforms; diff --git a/src/main/java/org/example/chaosgame/chaos/ChaosGameFileHandler.java b/src/main/java/org/example/chaosgame/chaos/ChaosGameFileHandler.java index db43530cd87e6e8b7277af48fb2d63df6493e0a6..8c955ffbbf2e96869745a9cea9b4d7a432d85c08 100644 --- a/src/main/java/org/example/chaosgame/chaos/ChaosGameFileHandler.java +++ b/src/main/java/org/example/chaosgame/chaos/ChaosGameFileHandler.java @@ -1,10 +1,140 @@ package org.example.chaosgame.chaos; +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; +import java.util.Scanner; +import org.example.chaosgame.linalg.Complex; +import org.example.chaosgame.linalg.Matrix2x2; +import org.example.chaosgame.linalg.Vector2D; +import org.example.chaosgame.transformations.AffineTransform2D; +import org.example.chaosgame.transformations.JuliaTransform; +import org.example.chaosgame.transformations.Transform2D; + +/** + * Class for reading and writing chaos game descriptions from and to files. + */ public class ChaosGameFileHandler { - public ChaosGameDescription readFromFile(String path){ - return null; + private final List<Transform2D> transforms = new ArrayList<>(); + + /** + * Reads a chaos game description from a file. + * + * <p>The text files should have the following format: + * + * <p>First line: type of transformation (Affine2D or Julia) + * + * <p>Second line: minimum coordinates of the canvas (x, y) + * + * <p>Third line: maximum coordinates of the canvas (x, y) + * + * <p>Fourth line and onwards: the transformations + * + * @param path the path to the file + * @return the chaos game description + * @throws IOException if the file cannot be read + */ + public ChaosGameDescription readFromFile(String path) throws IOException { + Vector2D minCoords; + Vector2D maxCoords; + try (Scanner scanner = new Scanner(new File(path))) { + scanner.useLocale(Locale.ENGLISH); + + String typeOfTransformation = skipComments(scanner.nextLine()); + System.out.println("Parsing type of transformation: " + typeOfTransformation); + + minCoords = parseVector(scanner.nextLine().trim()); + maxCoords = parseVector(scanner.nextLine().trim()); + + transforms.clear(); + + while (scanner.hasNextLine()) { + transforms.add(selectTransformation(typeOfTransformation, scanner)); + } + + } + return new ChaosGameDescription(minCoords, maxCoords, transforms); + } + + public void writeToFile(ChaosGameDescription description, String path) throws IOException { + + } + + /** + * Selects the correct transformation based on the type of transformation. + * + * @param typeOfTransformation a string with the name of the transformation + * @param scanner the scanner to read the transformation from + * @return the transformation + */ + private Transform2D selectTransformation(String typeOfTransformation, Scanner scanner) { + return switch (typeOfTransformation) { + case "Affine2D" -> parseAffine(scanner.nextLine()); + case "Julia" -> parseJulia(scanner.nextLine()); + default -> throw new IllegalArgumentException( + "Unknown type of transformation: " + typeOfTransformation); + }; + } + + /** + * Skips everything after the first # in a line. + * + * @param line a line of text + * @return the first part of the line + */ + private String skipComments(String line) { + String[] parts = line.split("#"); + return parts[0].trim(); + } + + /** + * Parses a vector from a string. + * + * @param line a line of text + * @return the vector + */ + private Vector2D parseVector(String line) { + String numbers = skipComments(line); + System.out.println("Parsing vector: " + numbers); + String[] vectorParts = numbers.split(","); + double x = Double.parseDouble(vectorParts[0].trim()); + double y = Double.parseDouble(vectorParts[1].trim()); + return new Vector2D(x, y); + } + + /** + * Parses an affine transformation from a string. + * + * @param line a line of text + * @return the transformation + */ + private Transform2D parseAffine(String line) { + String numbers = skipComments(line); + System.out.println("Parsing transform: " + numbers); + String[] transformParts = numbers.split(","); + double a = Double.parseDouble(transformParts[0].trim()); + double b = Double.parseDouble(transformParts[1].trim()); + double c = Double.parseDouble(transformParts[2].trim()); + double d = Double.parseDouble(transformParts[3].trim()); + double x = Double.parseDouble(transformParts[4].trim()); + double y = Double.parseDouble(transformParts[5].trim()); + return new AffineTransform2D(new Matrix2x2(a, b, c, d), new Vector2D(x, y)); } - public void writeToFile(String path){ + /** + * Parses a Julia transformation from a string. + * + * @param line a line of text + * @return the transformation + */ + private Transform2D parseJulia(String line) { + String numbers = skipComments(line); + System.out.println("Parsing transform: " + numbers); + String[] transformParts = numbers.split(","); + double r = Double.parseDouble(transformParts[0].trim()); + double i = Double.parseDouble(transformParts[1].trim()); + return new JuliaTransform(new Complex(r, i), 1); } } diff --git a/src/main/java/org/example/chaosgame/chaos/ExploreGame.java b/src/main/java/org/example/chaosgame/chaos/ExploreGame.java index 7d888a53cf93b29b20c2ac09304899a000a76f44..674a9fa02eadedea443f049682546d24eed39079 100644 --- a/src/main/java/org/example/chaosgame/chaos/ExploreGame.java +++ b/src/main/java/org/example/chaosgame/chaos/ExploreGame.java @@ -28,8 +28,7 @@ public class ExploreGame { Vector2D tempPoint = currentPoint; while (iter < MAX_ITER && tempPoint.getX() >= description.getMinCoords().getX() && tempPoint.getX() <= description.getMaxCoords().getX() && tempPoint.getY() >= description.getMinCoords().getY() && tempPoint.getY() <= description.getMaxCoords().getY()){ - int randomIndex = random.nextInt(description.getTransforms().size()); - tempPoint = description.getTransforms().get(randomIndex).transform(tempPoint); + tempPoint = description.getTransforms().getFirst().transform(tempPoint); iter++; } canvas.putPixel(x, y, iter); diff --git a/src/main/java/org/example/chaosgame/linalg/Complex.java b/src/main/java/org/example/chaosgame/linalg/Complex.java index 6371b92304cb0a0b2face1c8e0a1912d9096ba5c..5e036b4f78dc945f34eb1e3b74cf62c410a89f31 100644 --- a/src/main/java/org/example/chaosgame/linalg/Complex.java +++ b/src/main/java/org/example/chaosgame/linalg/Complex.java @@ -14,11 +14,11 @@ public class Complex extends Vector2D { * Constructor for Complex class. * super(x, y) is used to call the constructor of the superclass, Vector2D. * - * @param x x-coordinate. - * @param y y-coordinate. + * @param real x-coordinate. + * @param imaginary y-coordinate. */ - public Complex(double x, double y) { - super(x, y); + public Complex(double real, double imaginary) { + super(real, imaginary); } /** @@ -36,4 +36,5 @@ public class Complex extends Vector2D { return new Complex(r, i); } + } diff --git a/src/main/java/org/example/chaosgame/transformations/AffineTransform2D.java b/src/main/java/org/example/chaosgame/transformations/AffineTransform2D.java index b1b46b251b30f900f3f64c882ddcc5ff55d2c2b6..db3a2d699d6dfac7def408215ad382576ac11495 100644 --- a/src/main/java/org/example/chaosgame/transformations/AffineTransform2D.java +++ b/src/main/java/org/example/chaosgame/transformations/AffineTransform2D.java @@ -3,7 +3,13 @@ package org.example.chaosgame.transformations; import org.example.chaosgame.linalg.Matrix2x2; import org.example.chaosgame.linalg.Vector2D; -public class AffineTransform2D implements Transform2D{ +/** + * Represents an affine transformation in 2D space. + * The transformation is represented by a 2x2 matrix and a 2D vector. + * The transformation is applied to a 2D point by first multiplying the point with the matrix + * and then adding the vector. + */ +public class AffineTransform2D implements Transform2D { private final Matrix2x2 matrix; private final Vector2D vector; @@ -12,6 +18,14 @@ public class AffineTransform2D implements Transform2D{ this.vector = vector; } + /** + * Transforms a 2D point using this affine transformation. + * Overridden from the Transform2D interface. + * + * @param point the point to transform + * + * @return the transformed point + */ @Override public Vector2D transform(Vector2D point) { return matrix.multiply(point).add(vector); diff --git a/src/main/java/org/example/chaosgame/transformations/JuliaTransform.java b/src/main/java/org/example/chaosgame/transformations/JuliaTransform.java index f388a1d3f4a0c54290917d1ae47489124c8a396e..4b8f2c065cb4bf89769db0637559262bd44a7a6b 100644 --- a/src/main/java/org/example/chaosgame/transformations/JuliaTransform.java +++ b/src/main/java/org/example/chaosgame/transformations/JuliaTransform.java @@ -3,7 +3,16 @@ package org.example.chaosgame.transformations; import org.example.chaosgame.linalg.Complex; import org.example.chaosgame.linalg.Vector2D; -public class JuliaTransform implements Transform2D{ +/** + * Class for the Julia transformation. + * The transformation is given by the formula: + * <br> + * <span style="font-family: Courier"> + * z → ±√̅z̅ ̅-̅ ̅c + *</span> + * + */ +public class JuliaTransform implements Transform2D { private final Complex point; private final int sign; diff --git a/src/main/java/org/example/chaosgame/transformations/Transform2D.java b/src/main/java/org/example/chaosgame/transformations/Transform2D.java index 5aab73454d2121204a9d2f2ba77633d876968975..461b9dbf5c569007c4cf08cd805495f589097660 100644 --- a/src/main/java/org/example/chaosgame/transformations/Transform2D.java +++ b/src/main/java/org/example/chaosgame/transformations/Transform2D.java @@ -2,7 +2,10 @@ package org.example.chaosgame.transformations; import org.example.chaosgame.linalg.Vector2D; -public interface Transform2D{ +/** + * Interface for 2D transformations. + */ +public interface Transform2D { public Vector2D transform(Vector2D point); } diff --git a/src/main/resources/barnsley.txt b/src/main/resources/barnsley.txt new file mode 100644 index 0000000000000000000000000000000000000000..8810e8694cd1c9124499bbcc1917242f262c3745 --- /dev/null +++ b/src/main/resources/barnsley.txt @@ -0,0 +1,7 @@ +Affine2D # Type of transformation +-2.65, 0 # Min-coordinate +2.65, 10 # Max-coordinate +0, 0, 0, .16, 0, 0 # 1st transform +.85, .04, -.04, .85, 0, 1.6 # 2nd transform +.2, -.26, .23, .22, 0, 1.6 # 3rd transform +-.15, .28, .26, .24, 0, .44 # 4th transform \ No newline at end of file diff --git a/src/main/resources/julia.txt b/src/main/resources/julia.txt new file mode 100644 index 0000000000000000000000000000000000000000..118baee3ccbaffae380b35773c494cdc3d3e75e9 --- /dev/null +++ b/src/main/resources/julia.txt @@ -0,0 +1,4 @@ +Julia # Type of transformation +-1.6, -1 # Min-coordinates +1.6, 1 # Max-coordinates +-0.70176, -0.3842 # Real and Imaginary part of the constant c \ No newline at end of file diff --git a/src/main/resources/sierpinski.txt b/src/main/resources/sierpinski.txt new file mode 100644 index 0000000000000000000000000000000000000000..7132287bf61ac6ea7a92a4efa6e3c70203f02ad2 --- /dev/null +++ b/src/main/resources/sierpinski.txt @@ -0,0 +1,6 @@ +Affine2D # Type of transformation +0, 0 # Min-coordinate +1, 1 # Max-coordinate +.5, 0, 0, .5, 0, 0 # 1st transform +.5, 0, 0, .5, .25, .5 # 2nd transform +.5, 0, 0, .5, .5, 0 # 3rd transform \ No newline at end of file