Skip to content
Snippets Groups Projects
Commit 5f517364 authored by Edvard Berdal Eek's avatar Edvard Berdal Eek
Browse files

Merged main into feat/exploregame

parents b0f8649a ad0da40a
No related branches found
No related tags found
1 merge request!10Add Explore game class and ExploreJulia
Pipeline #288204 passed
Showing
with 330 additions and 52 deletions
......@@ -6,3 +6,5 @@
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml
# GitHub Copilot persisted chat sessions
/copilot/chatSessions
......@@ -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);
}
}
......
......@@ -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;
}
}
}
}
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);
}
}
}
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;
......
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);
}
}
......@@ -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);
......
......@@ -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);
}
}
......@@ -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);
......
......@@ -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 → &#177;&radic;&#x305;z&#x305; &#x305;-&#x305; &#x305;c
*</span>
*
*/
public class JuliaTransform implements Transform2D {
private final Complex point;
private final int sign;
......
......@@ -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);
}
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
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
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
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment