From 33ba6b4b9c046a39571c25e64d41b5e9a85c7abd Mon Sep 17 00:00:00 2001
From: Edvard <edvardee@stud.ntnu.no>
Date: Tue, 21 May 2024 19:46:23 +0200
Subject: [PATCH] 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.
---
 .../chaosgame/controller/CanvasPainter.java   | 100 ++++++++++++++++++
 .../controller/ChaosGameController.java       |  11 +-
 .../controller/ExploreGameController.java     |  25 +++--
 .../chaosgame/model/chaos/ChaosGame.java      |   3 +
 .../chaosgame/model/chaos/ExploreGame.java    |  18 ++--
 .../org/example/chaosgame/view/GamePage.java  |  99 +----------------
 6 files changed, 142 insertions(+), 114 deletions(-)
 create mode 100644 src/main/java/org/example/chaosgame/controller/CanvasPainter.java

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 0000000..aac42a9
--- /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 d0ab8a8..b21ae7c 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 b6ae5e8..a72fe1f 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 bb6cb82..03922e8 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 16aa2be..a7cd8ea 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 8217f98..9e74a74 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();
   }
 }
-- 
GitLab