diff --git a/src/main/java/org/example/chaosgame/controller/CanvasPainter.java b/src/main/java/org/example/chaosgame/controller/CanvasPainter.java new file mode 100644 index 0000000000000000000000000000000000000000..aac42a91c621cbdc01595f7c95a60449754f9220 --- /dev/null +++ b/src/main/java/org/example/chaosgame/controller/CanvasPainter.java @@ -0,0 +1,100 @@ +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; + } +} diff --git a/src/main/java/org/example/chaosgame/controller/ChaosGameController.java b/src/main/java/org/example/chaosgame/controller/ChaosGameController.java index d0ab8a8d1565eccaa6227af4f522ce5313cf1f3c..b21ae7c80c225b79e1fbd48e346ebc5bab1583ff 100644 --- a/src/main/java/org/example/chaosgame/controller/ChaosGameController.java +++ b/src/main/java/org/example/chaosgame/controller/ChaosGameController.java @@ -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()); } /** diff --git a/src/main/java/org/example/chaosgame/controller/ExploreGameController.java b/src/main/java/org/example/chaosgame/controller/ExploreGameController.java index b6ae5e8cfac2ea62c8c1e05d13df1bce44c5b727..a72fe1fc9ed44263d6cd94ce3bea80c014337fd1 100644 --- a/src/main/java/org/example/chaosgame/controller/ExploreGameController.java +++ b/src/main/java/org/example/chaosgame/controller/ExploreGameController.java @@ -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()); diff --git a/src/main/java/org/example/chaosgame/model/chaos/ChaosGame.java b/src/main/java/org/example/chaosgame/model/chaos/ChaosGame.java index bb6cb820fea085da42c6e3833df1692b8f176b4a..03922e87194fefc3089ad23fa8d70a1de9180c1c 100644 --- a/src/main/java/org/example/chaosgame/model/chaos/ChaosGame.java +++ b/src/main/java/org/example/chaosgame/model/chaos/ChaosGame.java @@ -1,6 +1,7 @@ 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; diff --git a/src/main/java/org/example/chaosgame/model/chaos/ExploreGame.java b/src/main/java/org/example/chaosgame/model/chaos/ExploreGame.java index 16aa2be8aad9ec1f1d27b468fd3e858ae91be351..a7cd8ea5c2e342d595ad24f83d6314ed0cd99bd9 100644 --- a/src/main/java/org/example/chaosgame/model/chaos/ExploreGame.java +++ b/src/main/java/org/example/chaosgame/model/chaos/ExploreGame.java @@ -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); + } } diff --git a/src/main/java/org/example/chaosgame/view/GamePage.java b/src/main/java/org/example/chaosgame/view/GamePage.java index 8217f98513996bd19e21e588524516d3545d3cee..9e74a74b3e8e44bca60bfeca6531183f92efd14c 100644 --- a/src/main/java/org/example/chaosgame/view/GamePage.java +++ b/src/main/java/org/example/chaosgame/view/GamePage.java @@ -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(); } }