diff --git a/src/main/java/org/example/chaosgame/controller/ExploreGameController.java b/src/main/java/org/example/chaosgame/controller/ExploreGameController.java index 90a1ba0998309b7cc4be8abf2980f6b85b7ea080..4e6152ee0bfe71a848cf9f08af4a6b96defebc29 100644 --- a/src/main/java/org/example/chaosgame/controller/ExploreGameController.java +++ b/src/main/java/org/example/chaosgame/controller/ExploreGameController.java @@ -40,6 +40,9 @@ public class ExploreGameController implements Observer, Subject, GameController private static final int WIDTH = 1200; private static final int HEIGHT = 800; + private Vector2D minCoords = new Vector2D(-1.6, -1); + private Vector2D maxCoords = new Vector2D(1.6, 1); + /** * Constructor for ExploreGameController. * Initializes the ExploreGame and ExplorePage. @@ -47,9 +50,9 @@ public class ExploreGameController implements Observer, Subject, GameController public ExploreGameController() { ExploreJulia exploreJulia = new ExploreJulia(new Complex(-0.835, 0.2321)); this.trans = List.of(exploreJulia); - this.description = new ChaosGameDescription( - new Vector2D(-1.6, -1), - new Vector2D(1.6, 1), trans); + this.description = new ChaosGameDescription( + minCoords, + maxCoords, trans); this.exploreGame = new ExploreGame(description, WIDTH, HEIGHT); this.chaosCanvas = exploreGame.getCanvas(); this.pageObservers = new ArrayList<>(); @@ -100,11 +103,9 @@ public class ExploreGameController implements Observer, Subject, GameController Vector2D dragDistance = new Vector2D(event.getX(), canvas.getHeight() - event.getY()).subtract(dragStartTemp); Vector2D fractalRange = description.getMaxCoords().subtract(description.getMinCoords()); - Vector2D adjustedDragDistance = dragDistance.multiply(fractalRange) - .divide(new Vector2D(canvas.getWidth(), canvas.getHeight())); - Vector2D newMinCoords = description.getMinCoords().subtract(adjustedDragDistance); - Vector2D newMaxCoords = description.getMaxCoords().subtract(adjustedDragDistance); - description = new ChaosGameDescription(newMinCoords, newMaxCoords, trans); + Vector2D adjustedDragDistance = dragDistance.multiply(fractalRange).divide(new Vector2D(canvas.getWidth(), canvas.getHeight())); + minCoords = description.getMinCoords().subtract(adjustedDragDistance); + maxCoords = description.getMaxCoords().subtract(adjustedDragDistance); updateExplorePage(); } @@ -117,78 +118,74 @@ public class ExploreGameController implements Observer, Subject, GameController */ public void zoomButtonClicked(double scaleFactor) { cumulativeScaleFactor *= scaleFactor; - Vector2D canvasCenter = chaosCanvas.transformIndicesToCoords( - chaosCanvas.getWidth() / 2, chaosCanvas.getHeight() / 2); - Vector2D newMinCoords = canvasCenter.subtract( - canvasCenter.subtract(description.getMinCoords()).scale(scaleFactor)); - Vector2D newMaxCoords = canvasCenter.add( - description.getMaxCoords().subtract(canvasCenter).scale(scaleFactor)); - - description = new ChaosGameDescription(newMinCoords, newMaxCoords, trans); + Vector2D canvasCenter = chaosCanvas.transformIndicesToCoords(chaosCanvas.getWidth() / 2, chaosCanvas.getHeight() / 2); + minCoords = canvasCenter.subtract(canvasCenter.subtract(description.getMinCoords()).scale(scaleFactor)); + maxCoords = canvasCenter.add(description.getMaxCoords().subtract(canvasCenter).scale(scaleFactor)); + updateExplorePage(); } - /** - * Method for handling scroll events. - * Zooms in or out based on the scroll direction. - * - * @param event ScrollEvent - */ - public void onScroll(ScrollEvent event) { - double mouseX = event.getX(); - double mouseY = event.getY(); - double scaleBase = event.isControlDown() ? 2 : 1.1; - double scaleFactor = 1; - double zoomInLimit = Math.pow(10, -15); - double zoomOutLimit = 8; - if (event.getDeltaY() > 0 && cumulativeScaleFactor > zoomInLimit) { - // Zoom in - scaleFactor = 1 / scaleBase; - } else if (event.getDeltaY() < 0 && cumulativeScaleFactor < zoomOutLimit) { - // Zoom out - scaleFactor = scaleBase; - } + /** + * Method for handling scroll events. + * Zooms in or out based on the scroll direction. + * + * @param event ScrollEvent + */ + public void onScroll (ScrollEvent event){ + double mouseX = event.getX(); + double mouseY = event.getY(); + double scaleBase = event.isControlDown() ? 2 : 1.1; + double scaleFactor = 1; + double zoomInLimit = Math.pow(10, -15); + double zoomOutLimit = 8; + if (event.getDeltaY() > 0 && cumulativeScaleFactor > zoomInLimit) { + // Zoom in + scaleFactor = 1 / scaleBase; + } else if (event.getDeltaY() < 0 && cumulativeScaleFactor < zoomOutLimit) { + // Zoom out + scaleFactor = scaleBase; + } - cumulativeScaleFactor *= scaleFactor; - double middleMouseX = mouseX - (double) chaosCanvas.getWidth() / 2; - double middleMouseY = mouseY - (double) chaosCanvas.getHeight() / 2; - double translateX = canvas.getTranslateX(); - double translateY = canvas.getTranslateY(); - - canvas.setScaleX(canvas.getScaleX() * scaleFactor); - canvas.setScaleY(canvas.getScaleY() * scaleFactor); - - double newTranslateX = (middleMouseX - translateX) * (scaleFactor - 1); - double newTranslateY = (middleMouseY - translateY) * (scaleFactor - 1); - double setTranslateX = translateX - newTranslateX; - double setTranslateY = translateY - newTranslateY; - canvas.setTranslateX(setTranslateX); - canvas.setTranslateY(setTranslateY); - - Vector2D canvasCenter = chaosCanvas.transformIndicesToCoords((int) mouseX, (int) mouseY); - Vector2D newMinCoords = canvasCenter.subtract( - canvasCenter.subtract(description.getMinCoords()).scale(scaleFactor)); - Vector2D newMaxCoords = canvasCenter.add( - description.getMaxCoords().subtract(canvasCenter).scale(scaleFactor)); - - description = new ChaosGameDescription(newMinCoords, newMaxCoords, trans); - updateExplorePage(); + cumulativeScaleFactor *= scaleFactor; + double middleMouseX = mouseX - (double) chaosCanvas.getWidth() / 2; + double middleMouseY = mouseY - (double) chaosCanvas.getHeight() / 2; + double translateX = canvas.getTranslateX(); + double translateY = canvas.getTranslateY(); + + canvas.setScaleX(canvas.getScaleX() * scaleFactor); + canvas.setScaleY(canvas.getScaleY() * scaleFactor); + + double newTranslateX = (middleMouseX - translateX) * (scaleFactor - 1); + double newTranslateY = (middleMouseY - translateY) * (scaleFactor - 1); + double setTranslateX = translateX - newTranslateX; + double setTranslateY = translateY - newTranslateY; + canvas.setTranslateX(setTranslateX); + canvas.setTranslateY(setTranslateY); + + Vector2D canvasCenter = chaosCanvas.transformIndicesToCoords((int) mouseX, (int) mouseY); + minCoords = canvasCenter.subtract(canvasCenter.subtract(description.getMinCoords()).scale(scaleFactor)); + maxCoords = canvasCenter.add(description.getMaxCoords().subtract(canvasCenter).scale(scaleFactor)); + updateExplorePage(); } + private void updateExplorePage() { - exploreGame.removeObserver(this); - exploreGame = new ExploreGame(description, (int) canvas.getWidth(), (int) canvas.getHeight()); - exploreGame.registerObserver(this); + this.description.setMinCoords(minCoords); + this.description.setMaxCoords(maxCoords); + this.description.setTransforms(trans); + this.exploreGame = new ExploreGame(description, (int) canvas.getWidth(),(int) canvas.getHeight()); + this.exploreGame.registerObserver(this); this.chaosCanvas = exploreGame.getCanvas(); exploreGame.exploreFractals(); - explorePage.updateCanvas(this.chaosCanvas); + explorePage.updateCanvas(exploreGame.getCanvas()); - canvas.setTranslateX(0); - canvas.setTranslateY(0); - canvas.setScaleY(1); - canvas.setScaleX(1); + this.canvas.setTranslateX(0); + this.canvas.setTranslateY(0); + this.canvas.setScaleX(1); + this.canvas.setScaleY(1); } + public void homeButtonClicked() { notifyObservers(); } @@ -213,11 +210,12 @@ public class ExploreGameController implements Observer, Subject, GameController * Resets the image to the default position and scale. */ public void resetImage() { - Vector2D newMinCoords = new Vector2D(-1.6, -1); - Vector2D newMaxCoords = new Vector2D(1.6, 1); + minCoords = new Vector2D(-1.6, -1); + maxCoords = new Vector2D(1.6, 1); description = new ChaosGameDescription( - newMinCoords, - newMaxCoords, trans); + minCoords, + maxCoords, trans); + cumulativeScaleFactor = 1; updateExplorePage(); } @@ -250,7 +248,7 @@ public class ExploreGameController implements Observer, Subject, GameController ? value : exploreTransform.getComplex().getY(); trans = List.of(new ExploreJulia(new Complex(realPart, imaginaryPart))); - description.setTransforms(trans); +// description.setTransforms(trans); updateExplorePage(); } diff --git a/src/main/java/org/example/chaosgame/model/chaos/ChaosCanvas.java b/src/main/java/org/example/chaosgame/model/chaos/ChaosCanvas.java index 2f9e22c8f657259504d8d6ae0c13df01f3b781e8..6d1892f3b2e43a33cca7e001c35cae9e562b3822 100644 --- a/src/main/java/org/example/chaosgame/model/chaos/ChaosCanvas.java +++ b/src/main/java/org/example/chaosgame/model/chaos/ChaosCanvas.java @@ -88,6 +88,11 @@ public class ChaosCanvas { this.height = height; } + + public AffineTransform2D getTransformCoordsToIndices() { + return transformCoordsToIndices; + } + /** * Calculates the Affine transformation that maps coordinates to indices in the canvas array. * The transformation is calculated based on the width, height, minimum and maximum coordinates. @@ -104,6 +109,7 @@ public class ChaosCanvas { ((height - 1.0) * maxCoords.getY()) / (maxCoords.getY() - minCoords.getY()), ((width - 1.0) * minCoords.getX()) / (minCoords.getX() - maxCoords.getX()) )); + } /** @@ -159,10 +165,20 @@ public class ChaosCanvas { * Clears the canvas by setting all pixel values to 0. */ public void clearCanvas() { - for (int i = 0; i < height; i++) { - for (int j = 0; j < width; j++) { - canvas[i][j] = 0; + for (int i = 0; i < height; i++) { + for (int j = 0; j < width; j++) { + canvas[i][j] = 0; + } } + } + + public double getPixel(Vector2D point) { + Vector2D indices = transformCoordsToIndices.transform(point); + int x = (int) indices.getX(); + int y = (int) indices.getY(); + if (x >= 0 && x < height && y >= 0 && y < width) { + return canvas[x][y]; } + return 0.0; } } 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 506e325997f5858ad48c1b3eb6b750749a5abc7b..8e046e06eb0ec6f8d491a82d792df5bdbd7dfc7f 100644 --- a/src/main/java/org/example/chaosgame/model/chaos/ExploreGame.java +++ b/src/main/java/org/example/chaosgame/model/chaos/ExploreGame.java @@ -1,5 +1,17 @@ package org.example.chaosgame.model.chaos; + +import javafx.application.Platform; +import javafx.concurrent.Task; +import javafx.scene.canvas.Canvas; +import org.example.chaosgame.model.linalg.Vector2D; +import org.example.chaosgame.model.transformations.Transform2D; +import org.example.chaosgame.view.components.AlertUtility; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.IntStream; +import java.util.stream.Stream; import java.util.ArrayList; import java.util.List; import java.util.stream.IntStream; @@ -7,12 +19,13 @@ import org.example.chaosgame.controller.interfaces.Observer; import org.example.chaosgame.controller.interfaces.Subject; import org.example.chaosgame.model.linalg.Vector2D; + /** * Class for exploring julia sets. */ 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; @@ -28,11 +41,15 @@ public class ExploreGame implements Subject { */ public ExploreGame(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()); this.gameObservers = new ArrayList<>(); } + public void setExploreGame(ChaosGameDescription description, int width, int height) { + this.description = description; + setChaosCanvas(description.getMinCoords(), description.getMaxCoords(), width, height); + + } public ChaosCanvas getCanvas() { return canvas; } @@ -41,11 +58,6 @@ public class ExploreGame implements Subject { return description; } - public void setExploreGame(ChaosGameDescription description, int width, int height) { - this.description = description; - setChaosCanvas(description.getMinCoords(), description.getMaxCoords(), width, height); - } - /** * Method for setting the chaos canvas. * diff --git a/src/test/java/org/example/chaosgame/model/chaos/ChaosCanvasTest.java b/src/test/java/org/example/chaosgame/model/chaos/ChaosCanvasTest.java index 6df0e369bf6d813e654a083cac56c0d2bf8369c9..f40039fca6451974a3406d9da0042e992d042987 100644 --- a/src/test/java/org/example/chaosgame/model/chaos/ChaosCanvasTest.java +++ b/src/test/java/org/example/chaosgame/model/chaos/ChaosCanvasTest.java @@ -1,78 +1,184 @@ package org.example.chaosgame.model.chaos; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; +import org.example.chaosgame.model.linalg.Vector2D; +import org.example.chaosgame.model.transformations.AffineTransform2D; +import org.junit.jupiter.api.*; import static org.junit.jupiter.api.Assertions.*; class ChaosCanvasTest { + private static ChaosCanvas chaosCanvas; + private static Vector2D minCoords; + private static Vector2D maxCoords; + private static final int WIDTH = 100; + private static final int HEIGHT = 100; @BeforeEach void setUp() { + minCoords = new Vector2D(0, 0); + maxCoords = new Vector2D(1, 1); + chaosCanvas = new ChaosCanvas(WIDTH, HEIGHT, minCoords, maxCoords); } @AfterEach void tearDown() { - } - - @Test - void getPixel() { - } - - @Test - void putPixel() { - } - - @Test - void testPutPixel() { - } - - @Test - void transformIndicesToCoords() { - } - - @Test - void getCanvasArray() { + minCoords = null; + maxCoords = null; + chaosCanvas = null; + } + + @Nested + @DisplayName("Test getPixel") + class TestGetPixel { + @Test + @DisplayName("Test positive getPixel") + void getPixel() { + double pixel = chaosCanvas.getPixel(new Vector2D(0, 0)); + assertEquals(0.0, pixel); + } + + @Test + @DisplayName("Test negative getPixel") + void getPixelFail() { + double pixel = chaosCanvas.getPixel(new Vector2D(0, 0)); + assertNotEquals(1.0, pixel); + } + } + + @Nested + @DisplayName("Test putPixel") + class TestPutPixel { + @Test + @DisplayName("Test positive putPixel(Vector2D)") + void putPixel() { + chaosCanvas.putPixelChaos(new Vector2D(0, 0)); + assertEquals(1.0, chaosCanvas.getPixel(new Vector2D(0, 0))); + } + + @Test + @DisplayName("Test negative putPixel(Vector2D)") + void putPixelFail() { + chaosCanvas.putPixelChaos(new Vector2D(0, 0)); + assertNotEquals(0.0, chaosCanvas.getPixel(new Vector2D(0, 0))); + } + + @Test + @DisplayName("Test positive putPixel(int, int, double)") + void putPixelInt() { + chaosCanvas.putPixelExplore(0, 0, 1.0); + assertEquals(1.0, chaosCanvas.getPixel(new Vector2D(0, 1))); + } + + @Test + @DisplayName("Test negative putPixel(int, int, double)") + void putPixelIntFail() { + chaosCanvas.putPixelExplore(0, 0, 1.0); + assertNotEquals(0.0, chaosCanvas.getPixel(new Vector2D(0, 1))); + } + } + + @Nested + @DisplayName("Test transformIndicesToCoords") + class TestTransformIndicesToCoords { + @Test + @DisplayName("Test positive transformIndicesToCoords") + void transformIndicesToCoords() { + Vector2D coords = chaosCanvas.transformIndicesToCoords(0, 0); + assertEquals(0.0, coords.getX()); + assertEquals(1.0, coords.getY()); + } + + @Test + @DisplayName("Test negative transformIndicesToCoords") + void transformIndicesToCoordsFail() { + Vector2D coords = chaosCanvas.transformIndicesToCoords(0, 0); + assertNotEquals(1.0, coords.getX()); + assertNotEquals(0.0, coords.getY()); + } + } + + @Nested + @DisplayName("Test clearCanvas") + class TestClearCanvas { + @Test + @DisplayName("Test positive clearCanvas") + void clearCanvas() { + chaosCanvas.putPixelChaos(new Vector2D(0, 0)); + chaosCanvas.clearCanvas(); + assertEquals(0.0, chaosCanvas.getPixel(new Vector2D(0, 0))); + } + + @Test + @DisplayName("Test negative clearCanvas") + void clearCanvasFail() { + chaosCanvas.putPixelChaos(new Vector2D(0, 0)); + chaosCanvas.clearCanvas(); + double[][] canvas = chaosCanvas.getCanvasArray(); + for (double[] row : canvas) { + for (double value : row) { + assertNotEquals(1.0, value); + } + } + } } @Test void getWidth() { + assertEquals(WIDTH, chaosCanvas.getWidth()); } @Test void getHeight() { + assertEquals(HEIGHT, chaosCanvas.getHeight()); } @Test void getMinCoords() { + assertEquals(minCoords, chaosCanvas.getMinCoords()); } @Test void getMaxCoords() { + assertEquals(maxCoords, chaosCanvas.getMaxCoords()); } @Test void setMinCoords() { + Vector2D newMinCoords = new Vector2D(-1, -1); + chaosCanvas.setMinCoords(newMinCoords); + assertEquals(newMinCoords, chaosCanvas.getMinCoords()); } @Test void setMaxCoords() { + Vector2D newMaxCoords = new Vector2D(2, 2); + chaosCanvas.setMaxCoords(newMaxCoords); + assertEquals(newMaxCoords, chaosCanvas.getMaxCoords()); } @Test void setTransformCoordsToIndices() { - } - - @Test - void clearCanvas() { + Vector2D newMinCoords = new Vector2D(-1, -1); + Vector2D newMaxCoords = new Vector2D(2, 2); + chaosCanvas.setMinCoords(newMinCoords); + chaosCanvas.setMaxCoords(newMaxCoords); + chaosCanvas.setTransformCoordsToIndices(); + AffineTransform2D transform = chaosCanvas.getTransformCoordsToIndices(); + assertNotNull(transform); + assertEquals(transform, chaosCanvas.getTransformCoordsToIndices()); } @Test void setWidth() { + int newWidth = 200; + chaosCanvas.setWidth(newWidth); + assertEquals(newWidth, chaosCanvas.getWidth()); } @Test void setHeight() { + int newHeight = 200; + chaosCanvas.setHeight(newHeight); + assertEquals(newHeight, chaosCanvas.getHeight()); } -} \ No newline at end of file +}