diff --git a/src/main/java/org/example/chaosgame/MainApp.java b/src/main/java/org/example/chaosgame/MainApp.java index 20aa139361ea37500a14d55484af4cda2bd8e583..91e6367280a7385c54c81832968e74c2e347d4e8 100644 --- a/src/main/java/org/example/chaosgame/MainApp.java +++ b/src/main/java/org/example/chaosgame/MainApp.java @@ -6,10 +6,7 @@ import javafx.scene.control.Button; import javafx.scene.layout.BorderPane; import javafx.scene.layout.HBox; import javafx.stage.Stage; -import org.example.chaosgame.controller.MainController; -import org.example.chaosgame.model.chaos.ChaosGame; import org.example.chaosgame.model.chaos.ChaosGameDescription; -import org.example.chaosgame.model.chaos.ChaosGameDescriptionFactory; import org.example.chaosgame.model.chaos.ChaosGameFileHandler; import org.example.chaosgame.view.ChaosPage; import org.example.chaosgame.view.ExplorePage; diff --git a/src/main/java/org/example/chaosgame/controller/MainController.java b/src/main/java/org/example/chaosgame/controller/ChaosGameController.java similarity index 55% rename from src/main/java/org/example/chaosgame/controller/MainController.java rename to src/main/java/org/example/chaosgame/controller/ChaosGameController.java index fe97afa8003b8bfe103da46b29dd8e84e26a189d..e47d5e13030dddd4b1030db77b4440a072ba1fb6 100644 --- a/src/main/java/org/example/chaosgame/controller/MainController.java +++ b/src/main/java/org/example/chaosgame/controller/ChaosGameController.java @@ -1,22 +1,23 @@ package org.example.chaosgame.controller; -import javafx.scene.layout.StackPane; import org.example.chaosgame.model.chaos.ChaosGame; import org.example.chaosgame.view.ChaosPage; -public class MainController implements Observer{ +public class ChaosGameController implements Observer{ private final ChaosGame chaosGame; - private final ChaosPage chaosPage; +// private final ChaosPage chaosPage; - public MainController(ChaosGame chaosGame) { + + + public ChaosGameController(ChaosGame chaosGame) { this.chaosGame = chaosGame; - this.chaosPage = new ChaosPage(); chaosGame.registerObserver(this); } @Override public void update() { - chaosPage.updateCanvas(); +// chaosPage.updateCanvas(); + System.out.println("ChaosGameController.update"); } } diff --git a/src/main/java/org/example/chaosgame/controller/ChaosGameSubject.java b/src/main/java/org/example/chaosgame/controller/Subject.java similarity index 81% rename from src/main/java/org/example/chaosgame/controller/ChaosGameSubject.java rename to src/main/java/org/example/chaosgame/controller/Subject.java index 36dd8621aa6f29fd57d63cb449a8be2e7b8af86d..79bd695fb2aefa71f338b147ce255ce8be548985 100644 --- a/src/main/java/org/example/chaosgame/controller/ChaosGameSubject.java +++ b/src/main/java/org/example/chaosgame/controller/Subject.java @@ -1,6 +1,6 @@ package org.example.chaosgame.controller; -public interface ChaosGameSubject { +public interface Subject { void registerObserver(Observer observer); void removeObserver(Observer observer); void notifyObservers(); 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 88ec0ecbffc6eadb086322dcbfb0d982502bc35e..19101023bdabacdb662f49c42459a9dc025ba611 100644 --- a/src/main/java/org/example/chaosgame/model/chaos/ChaosGame.java +++ b/src/main/java/org/example/chaosgame/model/chaos/ChaosGame.java @@ -4,7 +4,7 @@ import java.util.ArrayList; import java.util.List; import java.util.Random; -import org.example.chaosgame.controller.ChaosGameSubject; +import org.example.chaosgame.controller.Subject; import org.example.chaosgame.controller.Observer; import org.example.chaosgame.model.linalg.Vector2D; @@ -17,7 +17,7 @@ import org.example.chaosgame.model.linalg.Vector2D; * The new point is then drawn on the canvas. * This process is repeated a selected amount of steps. */ -public class ChaosGame implements ChaosGameSubject { +public class ChaosGame implements Subject { private final ChaosCanvas canvas; private final ChaosGameDescription description; diff --git a/src/main/java/org/example/chaosgame/model/chaos/ChaosGameDescription.java b/src/main/java/org/example/chaosgame/model/chaos/ChaosGameDescription.java index ca773de82ee80d0c4e3a3ec3bffcbf7c0da89381..107dd439fea917d3f43903891a8b95a516cf3e5c 100644 --- a/src/main/java/org/example/chaosgame/model/chaos/ChaosGameDescription.java +++ b/src/main/java/org/example/chaosgame/model/chaos/ChaosGameDescription.java @@ -11,8 +11,8 @@ import org.example.chaosgame.model.transformations.Transform2D; * and a list of transformations to apply to the points. */ public class ChaosGameDescription { - private final Vector2D minCoords; - private final Vector2D maxCoords; + private Vector2D minCoords; + private Vector2D maxCoords; private final List<Transform2D> transforms; private final List<Integer> probabilities; @@ -50,6 +50,13 @@ public class ChaosGameDescription { return maxCoords; } + public void setMinCoords(Vector2D minCoords) { + this.minCoords = minCoords; + } + public void setMaxCoords(Vector2D maxCoords) { + this.maxCoords = maxCoords; + } + public List<Transform2D> getTransforms() { return transforms; } 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 b0d356ac0b8de24f28c2a2f3ce69c7df893a3e26..1803a8012e248a74ec3dd02aecafbfd558ef1462 100644 --- a/src/main/java/org/example/chaosgame/model/chaos/ExploreGame.java +++ b/src/main/java/org/example/chaosgame/model/chaos/ExploreGame.java @@ -53,23 +53,7 @@ public class ExploreGame{ } }); -// for (int y = 0; y < canvas.getHeight(); y++) { -// for (int x = 0; x < canvas.getWidth(); x++) { -// int iter = 0; -// currentPoint = canvas.transformIndicesToCoords(x, y); -// Vector2D tempPoint = currentPoint; -// while (iter < MAX_ITER && tempPoint.lengthSQ() < 4){ -// tempPoint = description.getTransforms().getFirst().transform(tempPoint); -// iter++; -// } -// double abs = Math.sqrt(tempPoint.lengthSQ()); -// double smooth = iter - Math.log(Math.log(abs)) / Math.log(2); -// -// canvas.putPixel(x, y, smooth); -// -// } -// -// } + long end = System.currentTimeMillis(); System.out.println("Time taken: " + (end - start) + "ms"); } diff --git a/src/main/java/org/example/chaosgame/view/ChaosPage.java b/src/main/java/org/example/chaosgame/view/ChaosPage.java index 658dfd3e14c814da3219d96fffa31a48f7127172..3fdcf472af1274e02635c4aed526c2e2fef23ee7 100644 --- a/src/main/java/org/example/chaosgame/view/ChaosPage.java +++ b/src/main/java/org/example/chaosgame/view/ChaosPage.java @@ -12,28 +12,34 @@ import javafx.scene.input.MouseEvent; import javafx.scene.layout.StackPane; import javafx.scene.layout.VBox; import javafx.scene.paint.Color; -import org.example.chaosgame.model.chaos.ChaosCanvas; -import org.example.chaosgame.model.chaos.ChaosGame; -import org.example.chaosgame.model.chaos.ChaosGameDescriptionFactory; +import org.example.chaosgame.controller.ChaosGameController; +import org.example.chaosgame.model.chaos.*; import org.example.chaosgame.model.linalg.Complex; +import java.io.File; +import java.io.IOException; +import javafx.stage.FileChooser; +import java.io.BufferedReader; +import java.io.FileReader; public class ChaosPage { + ChaosGameController chaosGameController; private final StackPane chaosContent; private ChaosGame chaosGame; private ChaosCanvas chaosCanvas; private Complex c = new Complex(-0.70176, -0.3842); private final Button runStepsButton = new Button("Run Steps"); - private final Canvas canvas; - private final GraphicsContext gc; + private final Canvas canvas = new Canvas(1200, 800); + private final GraphicsContext gc ; private final Label errorLabel = new Label("Invalid input. Please enter a valid number."); private final VBox runStepsBox = new VBox(); public ChaosPage() { + chaosContent = new StackPane(); + gc = canvas.getGraphicsContext2D(); updateChaosGame("Julia"); chaosCanvas = chaosGame.getCanvas(); - canvas = new Canvas(chaosCanvas.getWidth(), chaosCanvas.getHeight()); - gc = canvas.getGraphicsContext2D(); + TextField stepsField = new TextField(); @@ -54,9 +60,9 @@ public class ChaosPage { } else { updateChaosGame(selectedGame); } - gc.clearRect(0, 0, canvas.getWidth(), canvas.getHeight()); updateCanvas(); }); + runStepsButton.setOnAction(e5 -> { if (!stepsField.getText().isEmpty()) { try { @@ -71,7 +77,25 @@ public class ChaosPage { } }); - runStepsBox.getChildren().addAll(contextMenu,stepsField, runStepsButton); + +Button openFileButton = new Button("Open File"); +openFileButton.setOnAction(e -> { + FileChooser fileChooser = new FileChooser(); + fileChooser.getExtensionFilters().add(new FileChooser.ExtensionFilter("TXT files (*.txt)", "*.txt")); + File selectedFile = fileChooser.showOpenDialog(null); + + if (selectedFile != null) { + try { + ChaosGameFileHandler fileHandler = new ChaosGameFileHandler(); + ChaosGameDescription description = fileHandler.readFromFile(selectedFile.getAbsolutePath()); + updateChaosGame(description); + } catch (IOException ex) { + ex.printStackTrace(); + } + } +}); + + runStepsBox.getChildren().addAll(contextMenu,stepsField, runStepsButton, openFileButton); runStepsBox.setSpacing(10); runStepsBox.setPadding(new Insets(10)); runStepsBox.setAlignment(Pos.CENTER_RIGHT); @@ -113,6 +137,14 @@ public class ChaosPage { private void updateChaosGame(String chaosGameType) { chaosGame = new ChaosGame(ChaosGameDescriptionFactory.get(chaosGameType, c), 1200, 800); + chaosGameController = new ChaosGameController(chaosGame); + chaosCanvas = chaosGame.getCanvas(); + gc.clearRect(0, 0, canvas.getWidth(), canvas.getHeight()); + } + private void updateChaosGame(ChaosGameDescription description){ + chaosGame = new ChaosGame(description, 1200, 800); + chaosGameController = new ChaosGameController(chaosGame); chaosCanvas = chaosGame.getCanvas(); + gc.clearRect(0, 0, canvas.getWidth(), canvas.getHeight()); } } diff --git a/src/main/java/org/example/chaosgame/view/ExplorePage.java b/src/main/java/org/example/chaosgame/view/ExplorePage.java index e5483593a439f0af5e165e2955439a4baa2ce20f..3aae4b46684b97979d13b53dc3f527c5f80554ee 100644 --- a/src/main/java/org/example/chaosgame/view/ExplorePage.java +++ b/src/main/java/org/example/chaosgame/view/ExplorePage.java @@ -1,5 +1,7 @@ package org.example.chaosgame.view; +import javafx.animation.AnimationTimer; +import javafx.application.Platform; import javafx.geometry.Pos; import javafx.scene.canvas.Canvas; import javafx.scene.canvas.GraphicsContext; @@ -27,6 +29,9 @@ public class ExplorePage { private Complex c = new Complex(-0.835, 0.2321); private final Canvas canvas; private final GraphicsContext gc; + private Vector2D dragStart; + private Vector2D dragStartTemp; + private final List<Transform2D> trans = List.of( new ExploreJulia(c) ); @@ -42,6 +47,7 @@ public class ExplorePage { private WritableImage offScreenImage; private PixelWriter pixelWriter; + private Vector2D dragDistance; public ExplorePage() { @@ -71,27 +77,55 @@ public class ExplorePage { zoomInButton.setOnMousePressed(event -> { - double scaleFactor = 1.05; - Vector2D newMinCoords = description.getMinCoords().scale(1 / scaleFactor); - Vector2D newMaxCoords = description.getMaxCoords().scale(1 / scaleFactor); - description = new ChaosGameDescription(newMinCoords, newMaxCoords, trans); - exploreGame = new ExploreGame(description, 1200, 800); -// - exploreGame.exploreFractals(); - updateCanvas(); + AnimationTimer zoomInTimer = new AnimationTimer() { + @Override + public void handle(long now) { + double scaleFactor = 1.05; + Vector2D canvasCenter = new Vector2D(canvas.getWidth() / 2, canvas.getHeight() / 2); + Vector2D fractalCenter = chaosCanvas.transformIndicesToCoords((int)canvasCenter.getX(), (int)canvasCenter.getY()); + Vector2D newMinCoords = fractalCenter.subtract(fractalCenter.subtract(description.getMinCoords()).scale(1 / scaleFactor)); + Vector2D newMaxCoords = fractalCenter.add(description.getMaxCoords().subtract(fractalCenter).scale(1 / scaleFactor)); + description = new ChaosGameDescription(newMinCoords, newMaxCoords, trans); + exploreGame = new ExploreGame(description, 1200, 800); + exploreGame.exploreFractals(); + updateCanvas(); + } + }; + zoomInTimer.start(); + zoomInButton.setUserData(zoomInTimer); // Store the timer in the button's user data + }); + zoomInButton.setOnMouseReleased(event -> { + AnimationTimer zoomInTimer = (AnimationTimer) zoomInButton.getUserData(); + if (zoomInTimer != null) { + zoomInTimer.stop(); + } }); - zoomOutButton.setOnAction(event -> { - double scaleFactor = 1 / 1.05; - Vector2D newMinCoords = description.getMinCoords().scale(1 / scaleFactor); - Vector2D newMaxCoords = description.getMaxCoords().scale(1 / scaleFactor); + zoomOutButton.setOnMousePressed(event -> { + AnimationTimer zoomOutTimer = new AnimationTimer() { + @Override + public void handle(long now) { + double scaleFactor = 1.0 / 1.05; + Vector2D canvasCenter = new Vector2D(canvas.getWidth() / 2, canvas.getHeight() / 2); + Vector2D fractalCenter = chaosCanvas.transformIndicesToCoords((int)canvasCenter.getX(), (int)canvasCenter.getY()); + Vector2D newMinCoords = fractalCenter.subtract(fractalCenter.subtract(description.getMinCoords()).scale(1 / scaleFactor)); + Vector2D newMaxCoords = fractalCenter.add(description.getMaxCoords().subtract(fractalCenter).scale(1 / scaleFactor)); description = new ChaosGameDescription(newMinCoords, newMaxCoords, trans); exploreGame = new ExploreGame(description, 1200, 800); -// chaosCanvas = exploreGame.getCanvas(); exploreGame.exploreFractals(); updateCanvas(); + } + }; + zoomOutTimer.start(); + zoomOutButton.setUserData(zoomOutTimer); // Store the timer in the button's user data + }); + zoomOutButton.setOnMouseReleased(event -> { + AnimationTimer zoomOutTimer = (AnimationTimer) zoomOutButton.getUserData(); + if (zoomOutTimer != null) { + zoomOutTimer.stop(); + } }); @@ -102,35 +136,94 @@ public class ExplorePage { exploreContent.getChildren().addAll(canvas, buttons); + Platform.runLater(() -> { + buttons.setLayoutX(canvas.getWidth() - buttons.getWidth()); + buttons.setLayoutY(0); + }); + exploreContent.addEventFilter(MouseEvent.MOUSE_PRESSED, event -> { try { + dragStart = new Vector2D(event.getX(), event.getY()); + double mouseX = event.getX() - canvas.getLayoutX(); double mouseY = event.getY() - canvas.getLayoutY(); - initialMousePosition = new Vector2D(mouseX, canvas.getHeight() - mouseY); + dragStartTemp = new Vector2D(mouseX, canvas.getHeight() - mouseY); +// initialMousePosition = new Vector2D(mouseX, canvas.getHeight() - mouseY); } catch (Exception e) { e.printStackTrace(); } }); exploreContent.addEventFilter(MouseEvent.MOUSE_DRAGGED, event -> { - Vector2D currentMousePosition = new Vector2D(event.getX(),canvas.getHeight() - event.getY()); - Vector2D dragDistance = currentMousePosition.subtract(initialMousePosition); +// Vector2D currentMousePosition = new Vector2D(event.getX(),canvas.getHeight() - event.getY()); +// Vector2D dragDistance = currentMousePosition.subtract(initialMousePosition); +// +// // Adjust the drag distance based on the zoom level +//// Convert the drag distance from canvas coordinates to fractal coordinates +// 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.setMinCoords(newMinCoords); +// description.setMaxCoords(newMaxCoords); +// exploreGame = new ExploreGame(description, 1200, 800); +// exploreGame.exploreFractals(); +// updateCanvas(); +// +// initialMousePosition = currentMousePosition; + + Vector2D dragEnd = new Vector2D(event.getX(), event.getY()); + dragDistance = dragEnd.subtract(dragStart); + + canvas.setTranslateX(canvas.getTranslateX() + dragDistance.getX()); + canvas.setTranslateY(canvas.getTranslateY() + dragDistance.getY()); + + dragStart = dragEnd; + - // Adjust the drag distance based on the zoom level -// Convert the drag distance from canvas coordinates to fractal coordinates - Vector2D fractalRange = description.getMaxCoords().subtract(description.getMinCoords()); - Vector2D adjustedDragDistance = dragDistance.multiply(fractalRange).divide(new Vector2D(canvas.getWidth(), canvas.getHeight())); + +}); + + exploreContent.setOnMouseReleased(event -> { + dragDistance = new Vector2D(event.getX(), canvas.getHeight() - event.getY()).subtract(dragStartTemp); +// Reset the position where the drag started + Vector2D fractalRange = description.getMaxCoords().subtract(description.getMinCoords()); + System.out.println("drag distance: " + dragDistance.getX() + ", " + dragDistance.getY()); + Vector2D adjustedDragDistance = dragDistance.multiply(fractalRange).divide(new Vector2D(canvas.getWidth(), canvas.getHeight())); + System.out.println("Adjusted drag distance: " + adjustedDragDistance.getX() + ", " + adjustedDragDistance.getY()); Vector2D newMinCoords = description.getMinCoords().subtract(adjustedDragDistance); Vector2D newMaxCoords = description.getMaxCoords().subtract(adjustedDragDistance); - - description = new ChaosGameDescription(newMinCoords, newMaxCoords, trans); + description.setMinCoords(newMinCoords); + description.setMaxCoords(newMaxCoords); exploreGame = new ExploreGame(description, 1200, 800); exploreGame.exploreFractals(); updateCanvas(); + System.out.println("Mouse released"); - initialMousePosition = currentMousePosition; -}); + dragStart = null; + canvas.setTranslateX(0); + canvas.setTranslateY(0); + }); + +//exploreContent.addEventFilter(MouseEvent.MOUSE_RELEASED, event -> { +// Vector2D currentMousePosition = new Vector2D(event.getX(), canvas.getHeight() - event.getY()); +// Vector2D dragDistance = currentMousePosition.subtract(initialMousePosition); +// +// // Adjust the drag distance based on the zoom level +// 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.setMinCoords(newMinCoords); +// description.setMaxCoords(newMaxCoords); +// exploreGame.setDescription(description); +// exploreGame.exploreFractals(); +// updateCanvas(); +// }); } public StackPane getExploreContent() { @@ -152,9 +245,10 @@ exploreContent.addEventFilter(MouseEvent.MOUSE_DRAGGED, event -> { for (int i = 0; i < chaosCanvas.getHeight(); i++) { for (int j = 0; j < chaosCanvas.getWidth(); j++) { double color = Math.min(canvasArray[i][j] * 3, 255); - if (color > 0 && color < 255) { - pixelWriter.setColor(j, i, Color.rgb((int) color, (int) color, (int) color)); - } +// if (color >= 0 && color <= 255) { + pixelWriter.setColor(j, i, Color.rgb((int) color, 0, 0)); +// } + } }