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

Fix major bug when creating affine fractal with certain matrices

In addition moved logic from GamePage to the new abstract CanvasPainter class.
Improve JavaDoc and remove redundant method calls in ExploreGameController.
parent 0e78e21f
No related branches found
No related tags found
1 merge request!21Fix major bug when creating affine fractal with certain matrices.
Pipeline #288564 passed
package org.example.chaosgame.controller;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.image.PixelWriter;
import javafx.scene.image.WritableImage;
import javafx.scene.paint.Color;
import org.example.chaosgame.model.chaos.ChaosCanvas;
/**
* Class for the CanvasPainter.
* The CanvasPainter is used for painting a fractal
* on the canvas.
* All methods are public and static.
*/
public abstract class CanvasPainter {
protected Color fractalColor = Color.WHITE;
private static final int COLOR_FACTOR = 3;
private static final int MAX_COLOR_VALUE = 255;
/**
* Method for painting the canvas.
* Draws the canvas on the graphics context.
* Uses the method createOffScreenImage to create the offscreen image.
*
* @param chaosCanvas the chaos canvas
* @param gc the graphics context
*/
private void drawCanvas(ChaosCanvas chaosCanvas, GraphicsContext gc) {
double[][] canvasArray = chaosCanvas.getCanvasArray();
double cellWidth = gc.getCanvas().getWidth() / chaosCanvas.getWidth();
double cellHeight = gc.getCanvas().getHeight() / chaosCanvas.getHeight();
WritableImage offScreenImage = createOffScreenImage(chaosCanvas, canvasArray);
gc.drawImage(offScreenImage, 0, 0, cellWidth * chaosCanvas.getWidth(),
cellHeight * chaosCanvas.getHeight());
}
/**
* Method for updating the canvas.
* Clears the canvas and draws the canvas.
*
* @param chaosCanvas the chaos canvas
*/
protected void updateCanvas(ChaosCanvas chaosCanvas, GraphicsContext gc) {
this.clearCanvas(gc);
drawCanvas(chaosCanvas, gc);
}
/**
* Method for clearing the canvas.
*
* @param gc the graphics context
*/
protected void clearCanvas(GraphicsContext gc) {
gc.clearRect(0, 0, gc.getCanvas().getWidth(), gc.getCanvas().getHeight());
}
/**
* Method for creating the offscreen image.
* Creates a WritableImage and sets the pixel color.
*
* @param chaosCanvas the chaos canvas
* @param canvasArray the canvas array
* @return the WritableImage
*/
private WritableImage createOffScreenImage(ChaosCanvas chaosCanvas, double[][] canvasArray) {
WritableImage offScreenImage = new WritableImage(
chaosCanvas.getWidth(), chaosCanvas.getHeight());
PixelWriter pixelWriter = offScreenImage.getPixelWriter();
Color minColor = Color.BLACK;
Color maxColor = fractalColor;
for (int i = 0; i < chaosCanvas.getHeight(); i++) {
for (int j = 0; j < chaosCanvas.getWidth(); j++) {
double iterationCount = Math.min(canvasArray[i][j] * COLOR_FACTOR, MAX_COLOR_VALUE);
if (iterationCount > 0 && iterationCount <= MAX_COLOR_VALUE) {
double t = iterationCount / MAX_COLOR_VALUE;
double red = (1 - t) * minColor.getRed() + t * maxColor.getRed();
double green = (1 - t) * minColor.getGreen() + t * maxColor.getGreen();
double blue = (1 - t) * minColor.getBlue() + t * maxColor.getBlue();
pixelWriter.setColor(j, i, Color.color(red, green, blue));
}
}
}
return offScreenImage;
}
/**
* Method for setting color of the fractal.
*
* @param newFractalColor the new fractal color
*/
protected void setFractalColor(Color newFractalColor) {
fractalColor = newFractalColor;
}
}
......@@ -37,14 +37,13 @@ import org.example.chaosgame.view.components.MinMaxDialog;
*
* <p>Implements the GameController interface.
*/
public class ChaosGameController implements Observer, Subject, GameController {
public class ChaosGameController extends CanvasPainter implements Observer, Subject, GameController {
private final ChaosGame chaosGame;
private final ChaosPage chaosPage;
private final List<Observer> pageObservers;
private static final int WIDTH = 1200;
private static final int HEIGHT = 800;
private Canvas canvas;
private double cumulativeScaleFactor = 1;
/**
* Constructor for the ChaosGameController.
......@@ -278,7 +277,7 @@ public class ChaosGameController implements Observer, Subject, GameController {
chaosGame.getDescription().getMinCoords(),
chaosGame.getDescription().getMaxCoords());
chaosGame.getCanvas().clearCanvas();
chaosPage.clearCanvas();
clearCanvas(chaosPage.getGraphicsContext());
}
@Override
......@@ -325,8 +324,8 @@ public class ChaosGameController implements Observer, Subject, GameController {
@Override
public void updateFractalColor(Color color) {
chaosPage.setFractalColor(color);
chaosPage.updateCanvas(chaosGame.getCanvas());
setFractalColor(color);
updateCanvas(chaosGame.getCanvas(), chaosPage.getGraphicsContext());
}
@Override
......@@ -344,7 +343,7 @@ public class ChaosGameController implements Observer, Subject, GameController {
chaosGame.getTotalSteps(),
chaosGame.getDescription().getMinCoords(),
chaosGame.getDescription().getMaxCoords());
chaosPage.updateCanvas(chaosGame.getCanvas());
updateCanvas(chaosGame.getCanvas(), chaosPage.getGraphicsContext());
}
/**
......
......@@ -26,7 +26,7 @@ import org.example.chaosgame.view.ExplorePage;
*
* <p>The controller implements the GameController, and is a Subject and Observer.
*/
public class ExploreGameController implements Observer, Subject, GameController {
public class ExploreGameController extends CanvasPainter implements Observer, Subject, GameController {
private ExploreGame exploreGame;
private final ExplorePage explorePage;
private ChaosCanvas chaosCanvas;
......@@ -58,6 +58,7 @@ public class ExploreGameController implements Observer, Subject, GameController
this.pageObservers = new ArrayList<>();
exploreGame.registerObserver(this);
this.explorePage = new ExplorePage(this);
}
public void setCanvas(Canvas canvas) {
......@@ -132,6 +133,13 @@ public class ExploreGameController implements Observer, Subject, GameController
/**
* Method for handling scroll events.
* Zooms in or out based on the scroll direction.
* Prevents zooming out too far by setting a zoom in/out limit
* and saving the cumulative scale factor.
* Allows for faster zooming when holding down the control key.
* <p>
* Inspired by:
* <a href="https://github.com/majidrouhani/idatt2003-gui-demo-mandelbrot">
* idatt2003-gui-demo-mandelbrot</a>
*
* @param event ScrollEvent
*/
......@@ -172,9 +180,16 @@ public class ExploreGameController implements Observer, Subject, GameController
maxCoords = canvasCenter.add(description.getMaxCoords()
.subtract(canvasCenter).scale(scaleFactor));
updateExplorePage();
event.consume();
}
/**
* Method for updating the ExplorePage.
* Updates the ExplorePage with the new ExploreGame.
* Updates the canvas with the new fractal.
* Resets the canvas position and scale.
*/
private void updateExplorePage() {
this.description.setMinCoords(minCoords);
this.description.setMaxCoords(maxCoords);
......@@ -184,8 +199,6 @@ public class ExploreGameController implements Observer, Subject, GameController
this.exploreGame.registerObserver(this);
this.chaosCanvas = exploreGame.getCanvas();
exploreGame.exploreFractals();
explorePage.updateCanvas(exploreGame.getCanvas());
this.canvas.setTranslateX(0);
this.canvas.setTranslateY(0);
this.canvas.setScaleX(1);
......@@ -208,8 +221,8 @@ public class ExploreGameController implements Observer, Subject, GameController
*/
@Override
public void updateFractalColor(Color color) {
explorePage.setFractalColor(color);
explorePage.updateCanvas(exploreGame.getCanvas());
setFractalColor(color);
updateCanvas(chaosCanvas, canvas.getGraphicsContext2D());
}
/**
......@@ -264,7 +277,7 @@ public class ExploreGameController implements Observer, Subject, GameController
*/
@Override
public void update() {
explorePage.updateCanvas(exploreGame.getCanvas());
updateCanvas(chaosCanvas, canvas.getGraphicsContext2D());
explorePage.updateInformation(
description.getTransforms().getFirst(),
description.getMinCoords(), description.getMaxCoords());
......
package org.example.chaosgame.model.chaos;
import java.util.ArrayList;
import java.util.Currency;
import java.util.List;
import java.util.Random;
import org.example.chaosgame.controller.interfaces.Observer;
......@@ -168,6 +169,7 @@ public class ChaosGame implements Subject {
* @param steps Number of steps to run
*/
private void runStepsUniform(int steps) {
currentPoint = new Vector2D(0.0, 0.0);
for (int i = 0; i < steps; i++) {
int transformIndex = random.nextInt(description.getTransforms().size());
currentPoint = description.getTransforms().get(transformIndex).transform(currentPoint);
......@@ -184,6 +186,7 @@ public class ChaosGame implements Subject {
* @param probabilities List of probabilities for the transformations
*/
private void runStepsWithProbabilities(int steps, List<Integer> probabilities) {
currentPoint = new Vector2D(0.0, 0.0);
for (int i = 0; i < steps; i++) {
int test = random.nextInt(100);
int transformIndex = -1;
......
......@@ -6,7 +6,7 @@ import java.util.stream.IntStream;
import org.example.chaosgame.controller.interfaces.Observer;
import org.example.chaosgame.controller.interfaces.Subject;
import org.example.chaosgame.model.linalg.Vector2D;
import org.example.chaosgame.model.transformations.Transform2D;
/**
......@@ -14,7 +14,7 @@ import org.example.chaosgame.model.linalg.Vector2D;
*/
public class ExploreGame implements Subject {
private static final int MAX_ITER = 256;
private final ChaosCanvas canvas;
private ChaosCanvas canvas;
private ChaosGameDescription description;
private Vector2D currentPoint = new Vector2D(0.0, 0.0);
private final List<Observer> gameObservers;
......@@ -72,18 +72,16 @@ public class ExploreGame implements Subject {
* @param height Height of the canvas
*/
public void setChaosCanvas(Vector2D minCoords, Vector2D maxCoords, int width, int height) {
this.canvas.clearCanvas();
this.canvas.setMaxCoords(maxCoords);
this.canvas.setMinCoords(minCoords);
this.canvas.setWidth(width);
this.canvas.setHeight(height);
this.canvas = new ChaosCanvas(width, height, minCoords, maxCoords);
}
/**
* Method for exploring fractals. Iterates over all pixels in the canvas
* and applies the transformation to the current point.
* Inspiration from <a href="https://www.youtube.com/watch?v=uc2yok_pLV4">Pezzza's Work</a>
* and <a href="https://github.com/majidrouhani/idatt2003-gui-demo-mandelbrot">idatt2003-gui-demo-mandelbrot</a>
* for the smoothing algorithm.
* And also <a href="https://github.com/majidrouhani/idatt2003-gui-demo-mandelbrot">idatt2003-gui-demo-mandelbrot</a>
* for the parallel stream.
*
*/
public void exploreFractals() {
......@@ -138,4 +136,8 @@ public class ExploreGame implements Subject {
gameObserver.update();
}
}
public void setDescription(Vector2D minCoords, Vector2D maxCoords, List<Transform2D> transforms) {
this.description = new ChaosGameDescription(minCoords, maxCoords, transforms);
}
}
......@@ -2,114 +2,25 @@ package org.example.chaosgame.view;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.image.PixelWriter;
import javafx.scene.image.WritableImage;
import javafx.scene.layout.BorderPane;
import javafx.scene.paint.Color;
import org.example.chaosgame.model.chaos.ChaosCanvas;
/**
* Abstract class for the GamePage, extends BorderPane.
* The GamePage is used for displaying the game.
*/
public abstract class GamePage extends BorderPane {
protected final GraphicsContext gc;
private static final int COLOR_FACTOR = 3;
private static final int CANVAS_WIDTH = 1250;
private static final int CANVAS_HEIGHT = 805;
private static final int MAX_COLOR_VALUE = 255;
protected Color fractalColor;
protected final GraphicsContext gc;
/**
* Constructor for the GamePage.
* Initializes the canvas and fractal color.
* Initializes the canvas and makes a new GraphicsContext
* available for subclasses.
*/
public GamePage() {
this.gc = createCanvas();
this.fractalColor = Color.WHITE;
}
/**
* Method for setting color of the fractal.
*
* @param newFractalColor the new fractal color
*/
public void setFractalColor(Color newFractalColor) {
this.fractalColor = newFractalColor;
}
/**
* Method for creating the canvas.
*
* @return the GraphicsContext
*/
private GraphicsContext createCanvas() {
Canvas canvas = new Canvas(CANVAS_WIDTH, CANVAS_HEIGHT);
return canvas.getGraphicsContext2D();
}
/**
* Method for updating the canvas.
* Clears the canvas and draws the canvas.
*
* @param chaosCanvas the chaos canvas
*/
public void updateCanvas(ChaosCanvas chaosCanvas) {
clearCanvas();
drawCanvas(chaosCanvas);
}
/**
* Method for clearing the canvas.
*/
public void clearCanvas() {
gc.clearRect(0, 0, gc.getCanvas().getWidth(), gc.getCanvas().getHeight());
}
/**
* Method for drawing the canvas.
*
* @param chaosCanvas the chaos canvas
*/
private void drawCanvas(ChaosCanvas chaosCanvas) {
double[][] canvasArray = chaosCanvas.getCanvasArray();
double cellWidth = gc.getCanvas().getWidth() / chaosCanvas.getWidth();
double cellHeight = gc.getCanvas().getHeight() / chaosCanvas.getHeight();
WritableImage offScreenImage = createOffScreenImage(chaosCanvas, canvasArray);
gc.drawImage(offScreenImage, 0, 0, cellWidth * chaosCanvas.getWidth(),
cellHeight * chaosCanvas.getHeight());
}
/**
* Method for creating the offscreen image.
* Creates a WritableImage and sets the pixel color.
*
* @param chaosCanvas the chaos canvas
* @param canvasArray the canvas array
* @return the WritableImage
*/
private WritableImage createOffScreenImage(ChaosCanvas chaosCanvas, double[][] canvasArray) {
WritableImage offScreenImage = new WritableImage(
chaosCanvas.getWidth(), chaosCanvas.getHeight());
PixelWriter pixelWriter = offScreenImage.getPixelWriter();
Color minColor = Color.BLACK;
Color maxColor = fractalColor;
for (int i = 0; i < chaosCanvas.getHeight(); i++) {
for (int j = 0; j < chaosCanvas.getWidth(); j++) {
double iterationCount = Math.min(canvasArray[i][j] * COLOR_FACTOR, MAX_COLOR_VALUE);
if (iterationCount > 0 && iterationCount <= MAX_COLOR_VALUE) {
double t = iterationCount / MAX_COLOR_VALUE;
double red = (1 - t) * minColor.getRed() + t * maxColor.getRed();
double green = (1 - t) * minColor.getGreen() + t * maxColor.getGreen();
double blue = (1 - t) * minColor.getBlue() + t * maxColor.getBlue();
pixelWriter.setColor(j, i, Color.color(red, green, blue));
}
}
}
return offScreenImage;
this.gc = canvas.getGraphicsContext2D();
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment