Skip to content
Snippets Groups Projects
Commit 2daadd0d authored by Håvard Daleng's avatar Håvard Daleng
Browse files

Changed the way the fractal image is generated and displayed such that a...

Changed the way the fractal image is generated and displayed such that a graphics context and Canvas class is used, and added button to show and hide the sidebar.
parent 395bcc4a
No related branches found
No related tags found
No related merge requests found
package edu.ntnu.stud.chaosgame;
import edu.ntnu.stud.chaosgame.view.ChaosGameGuiView;
import edu.ntnu.stud.chaosgame.view.ChaosGameGui;
import javafx.application.Application;
import javafx.stage.Stage;
......@@ -13,7 +13,7 @@ public class Main extends Application {
@Override
public void start(Stage primaryStage) throws IOException {
ChaosGameGuiView view = new ChaosGameGuiView(primaryStage);
ChaosGameGui view = new ChaosGameGui(primaryStage);
}
......
......@@ -175,7 +175,7 @@ public class ChaosGame {
* @return the resulting valid point within bounds.
*/
public Vector2D findValidPoint(Vector2D point) {
if (!this.canvas.isPointInCanvasRange(point)) {
if (!this.canvas.isPointInCanvasRange(point) || this.canvas.getPixel(point) == 1) {
while (!this.canvas.isPointInCanvasRange(point)) {
int j = this.random.nextInt(this.numOfTransforms);
System.out.println("Before transform: " + point.getX0() + " " + point.getX1()); // Test
......
package edu.ntnu.stud.chaosgame.controller.game;
import edu.ntnu.stud.chaosgame.controller.game.ChaosGame;
import edu.ntnu.stud.chaosgame.model.data.Vector2D;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.*;
// Step 1: Create a Callable tasks
class ChaosGameTask implements Callable<Void> {
private final int start;
private final int end;
private final ChaosGame game;
public ChaosGameTask(ChaosGame game, int start, int end) {
this.game = game;
this.start = start;
this.end = end;
}
@Override
public Void call() {
for (int i = start; i < end; i++) {
int j = game.getRandom().nextInt(game.getDescription().getTransforms().size());
Vector2D newPoint = game.getDescription().getTransforms().get(j).transform(game.getCurrentPoint());
game.setCurrentPoint(game.findValidPoint(newPoint));
game.getCanvas().putPixel(game.getCurrentPoint());
}
return null;
}
// Step 2, 3, 4: Divide the task into smaller tasks and execute them in parallel
public void runSteps(int n) {
int numProcessors = Runtime.getRuntime().availableProcessors();
ExecutorService executor = Executors.newFixedThreadPool(numProcessors);
int chunkSize = n / numProcessors;
List<Future<Void>> futures = new ArrayList<>();
for (int i = 0; i < numProcessors; i++) {
int start = i * chunkSize;
int end = (i == numProcessors - 1) ? n : start + chunkSize; // handle remainder
Callable<Void> task = new ChaosGameTask(this.game, start, end);
futures.add(executor.submit(task));
}
for (Future<Void> future : futures) {
try {
future.get(); // wait for task to complete
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
}
executor.shutdown(); // always remember to shutdown the executor
}
}
\ No newline at end of file
package edu.ntnu.stud.chaosgame.view;
import javafx.scene.image.PixelWriter;
import javafx.scene.image.WritableImage;
import javafx.scene.paint.Color;
import edu.ntnu.stud.chaosgame.controller.game.ChaosCanvas;
/**
* This class converts the state of a ChaosCanvas to a WritableImage.
*/
public class ChaosCanvasToImageConverter {
/**
* The image to be created.
*/
private WritableImage image;
/**
* Convert the canvas to a writable image.
*
* @param chaosCanvas the canvas to work upon.
*/
public ChaosCanvasToImageConverter(ChaosCanvas chaosCanvas) {
int width = chaosCanvas.getWidth();
int height = chaosCanvas.getHeight();
image = new WritableImage(width, height);
PixelWriter pixelWriter = image.getPixelWriter();
int[][] canvasArray = chaosCanvas.getCanvasArray();
for (int i = 0; i < width; i++) {
for (int j = 0; j < height; j++) {
if (canvasArray[i][j] == 1) {
pixelWriter.setColor(j, i, Color.BLACK);
} else {
pixelWriter.setColor(j, i, Color.WHITE);
}
}
}
}
/**
* Get the image.
*
* @return the image.
*/
public WritableImage getImage() {
return image;
}
}
......@@ -8,15 +8,18 @@ import edu.ntnu.stud.chaosgame.model.generators.ChaosGameDescriptionFactory;
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.animation.TranslateTransition;
import javafx.application.Platform;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.canvas.Canvas;
import javafx.scene.control.*;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.image.PixelWriter;
import javafx.scene.image.WritableImage;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Region;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
......@@ -25,13 +28,16 @@ import javafx.util.Duration;
import java.io.IOException;
import java.util.concurrent.atomic.AtomicReference;
public class ChaosGameGuiView implements ChaosGameObserver {
public class ChaosGameGui implements ChaosGameObserver {
private int currentLine = 0;
private Canvas canvas;
/**
* The ChaosCanvas for this GUI.
*/
private ChaosCanvas canvas;
private ChaosCanvas chaosCanvas;
/**
* The ChaosGameDescription.
......@@ -48,11 +54,6 @@ public class ChaosGameGuiView implements ChaosGameObserver {
*/
private AtomicReference<ChaosGameDescription> descriptionRef;
/**
* The PixelWriter for the GUI.
*/
private PixelWriter pixelWriter;
/**
* The ImageView for the GUI..
*/
......@@ -90,13 +91,14 @@ public class ChaosGameGuiView implements ChaosGameObserver {
private VBox sideMenu;
/**
* The start, stop, new, clear, and quit buttons for the GUI.
* The start, stop, new, clear, quit and show sidebar buttons for the GUI.
*/
private Button startButton;
private Button stopButton;
private Button newButton;
private Button clearButton;
private Button quitButton;
private Button sideMenuButton;
/**
* The load fractal from file and write fractal to file buttons for the GUI.
......@@ -113,7 +115,7 @@ public class ChaosGameGuiView implements ChaosGameObserver {
private RadioButton improvedBarnsleyButton;
public ChaosGameGuiView(Stage primaryStage) throws IOException {
public ChaosGameGui(Stage primaryStage) throws IOException {
this.initializeComponents();
......@@ -157,10 +159,8 @@ public class ChaosGameGuiView implements ChaosGameObserver {
this.newButton = new Button("New");
newButton.setOnAction(event ->{
WritableImage newWritableImage = new WritableImage(width, height);
setPixelWriter(newWritableImage.getPixelWriter());
setImageViewFromImage(newWritableImage);
canvas.clearCanvas();
this.canvas.getGraphicsContext2D().clearRect(0, 0, canvas.getWidth(), canvas.getHeight());
chaosCanvas.clearCanvas();
});
this.clearButton = new Button("Clear");
......@@ -185,8 +185,8 @@ public class ChaosGameGuiView implements ChaosGameObserver {
this.descriptionRef = new AtomicReference<>(factory.getDescriptions().get(0));
this.description = descriptionRef.get();
this.canvas = new ChaosCanvas(100, 100, descriptionRef.get().getMinCoords(), descriptionRef.get().getMaxCoords());
game = new ChaosGame(this.description, canvas);
this.chaosCanvas = new ChaosCanvas(100, 100, descriptionRef.get().getMinCoords(), descriptionRef.get().getMaxCoords());
game = new ChaosGame(this.description, chaosCanvas);
}
/**
......@@ -197,9 +197,8 @@ public class ChaosGameGuiView implements ChaosGameObserver {
this.imageView = new ChaosGameImageView(this);
width = 1000;
height = 1000;
WritableImage writableImage = new WritableImage(width, height);
pixelWriter = writableImage.getPixelWriter();
this.imageView.setImage(writableImage);
this.canvas = new Canvas(width, height);
this.imageView.setImage(canvas.snapshot(null, null));
this.clearImageView();
......@@ -210,9 +209,10 @@ public class ChaosGameGuiView implements ChaosGameObserver {
*/
private void clearImageView() {
// Color the image white
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
pixelWriter.setColor(x, y, Color.WHITE);
for (int i = 0; i < height; i++) {
for (int j = 0; j < width; j++) {
this.canvas.getGraphicsContext2D().setFill(Color.WHITE);
this.canvas.getGraphicsContext2D().fillRect(i, j, 1, 1);
}
}
......@@ -238,7 +238,7 @@ public class ChaosGameGuiView implements ChaosGameObserver {
improvedBarnsleyButton.setToggleGroup(group);
AtomicReference<ChaosCanvas> canvasRef = new AtomicReference<>(canvas);
AtomicReference<ChaosCanvas> canvasRef = new AtomicReference<>(chaosCanvas);
// Set action for Sierpinski radio button.
sierpinskiRadioButton.setOnAction(event -> {
......@@ -323,41 +323,61 @@ public class ChaosGameGuiView implements ChaosGameObserver {
// Add padding
sideMenu.setPadding(new Insets(10));
// Create split pane and button to toggle sidebar
this.sideMenuButton = new Button(">>");
this.initializeSideButtonHandler();
Region sideMenuButtonRegion = new Region();
sideMenuButtonRegion.setMinWidth(200);
HBox sideMenuButtonBox = new HBox();
sideMenuButtonBox.getChildren().addAll(sideMenuButtonRegion, sideMenuButton);
// The right VBox containing both the sidebar and the sidebar toggle button.
VBox rightVBox = new VBox();
rightVBox.getChildren().addAll(sideMenuButtonBox, sideMenu);
this.borderPane = new BorderPane();
this.borderPane.setCenter(imageView);
this.borderPane.setRight(sideMenu);
this.borderPane.setRight(rightVBox);
imageView.setFocusTraversable(true);
sideMenu.setFocusTraversable(false);
rightVBox.setFocusTraversable(false);
borderPane.setFocusTraversable(false);
}
private void initializeSideButtonHandler() {
TranslateTransition openNav = new TranslateTransition(new Duration(350), sideMenu);
openNav.setToX(0);
TranslateTransition closeNav = new TranslateTransition(new Duration(350), sideMenu);
this.sideMenuButton.setOnAction(e -> {
if(sideMenu.getTranslateX() != 0){
this.sideMenuButton.setText(">>");
openNav.play();
} else {
closeNav.setToX(sideMenu.getWidth());
closeNav.play();
this.sideMenuButton.setText("<<");
}
});
}
/**
* Get the chaos canvas of this GUI view.
*
* @return the canvas.
*/
public ChaosCanvas getCanvas() {
return this.canvas;
public ChaosCanvas getChaosCanvas() {
return this.chaosCanvas;
}
public void drawChaosGame(){
ChaosCanvas canvas = game.getCanvas();
game.runSteps(1000000);
// Test implementation for drawing fractals
int[][] betaArray = canvas.getCanvasArray();
for (int i = 0; i < canvas.getWidth(); i++) {
for (int j = 0; j < canvas.getHeight(); j++) {
if (betaArray[i][j] == 1) {
pixelWriter.setColor(j,i,Color.BLACK);
}
}
}
game.runSteps(100000000);
ChaosCanvasToImageConverter converter = new ChaosCanvasToImageConverter(this.chaosCanvas);
WritableImage image = converter.getImage();
this.canvas.getGraphicsContext2D().drawImage(image, 0, 0);
this.imageView.setImage(image);
}
public int getWidth(){
......@@ -374,10 +394,6 @@ public class ChaosGameGuiView implements ChaosGameObserver {
this.currentLine = currentLine;
}
public void setPixelWriter(PixelWriter pixelWriter) {
this.pixelWriter = pixelWriter;
}
public void setImageViewFromImage(Image inputView) {
this.imageView.setImage(inputView);
}
......@@ -391,18 +407,18 @@ public class ChaosGameGuiView implements ChaosGameObserver {
*/
@Override
public void updateDescription(int index) {
AtomicReference<ChaosCanvas> canvasRef = new AtomicReference<>(canvas);
AtomicReference<ChaosCanvas> canvasRef = new AtomicReference<>(chaosCanvas);
timeline.stop();
canvasRef.get().clearCanvas();
WritableImage newWritableImage = new WritableImage(width, height);
setPixelWriter(newWritableImage.getPixelWriter());
setImageViewFromImage(newWritableImage);
this.canvas.getGraphicsContext2D().clearRect(0, 0, this.canvas.getGraphicsContext2D().
getCanvas().getWidth(), this.canvas.getGraphicsContext2D().getCanvas().getHeight());
this.clearImageView();
canvas.clearCanvas();
chaosCanvas.clearCanvas();
this.descriptionRef.set(factory.getDescriptions().get(index)); // Assuming the Sierpinski description is at index 0
canvasRef.set(new ChaosCanvas(1000, 1000, this.descriptionRef.get().getMinCoords(), this.descriptionRef.get().getMaxCoords()));
this.canvas = canvasRef.get();
canvasRef.set(new ChaosCanvas(1000, 1000, this.descriptionRef.get().getMinCoords(),
this.descriptionRef.get().getMaxCoords()));
this.chaosCanvas = canvasRef.get();
game = new ChaosGame(this.descriptionRef.get(), canvasRef.get());
//this.game.setDescription(description);
}
......@@ -416,8 +432,8 @@ public class ChaosGameGuiView implements ChaosGameObserver {
*/
public void updateDetail(int zoomLevel, double centreX, double centreY) {
this.clearImageView();
this.canvas.clearCanvas();
this.canvas.updateCoords(centreX, centreY, zoomLevel);
this.chaosCanvas.clearCanvas();
this.chaosCanvas.updateCoords(centreX, centreY, zoomLevel);
this.game.setCurrentPoint(new Vector2D(centreX, centreY));
}
......
package edu.ntnu.stud.chaosgame.view;
import edu.ntnu.stud.chaosgame.controller.PopupManager;
import edu.ntnu.stud.chaosgame.controller.game.ChaosCanvas;
import edu.ntnu.stud.chaosgame.model.data.Vector2D;
import javafx.scene.image.ImageView;
import javafx.scene.input.ScrollEvent;
import javafx.scene.transform.Affine;
......@@ -16,7 +14,7 @@ public class ChaosGameImageView extends ImageView {
/**
* The controller for this class: a chaos game GUI view.
*/
private final ChaosGameGuiView controller;
private final ChaosGameGui controller;
/**
* Affine initialised to the identity matrix.
......@@ -50,8 +48,8 @@ public class ChaosGameImageView extends ImageView {
*
* @param controller the GUI which controls this image view.
*/
public ChaosGameImageView(ChaosGameGuiView controller) {
this.setOnScroll(this::zoom);
public ChaosGameImageView(ChaosGameGui controller) {
this.setOnScroll(this::guiZoom);
this.setOnMousePressed(this::mousePressed);
this.setOnMouseDragged(this::mouseDragged);
//this.setOnMouseReleased(this::mouseReleased);
......@@ -75,20 +73,30 @@ public class ChaosGameImageView extends ImageView {
*
* @param event the event.
*/
private synchronized void zoom(ScrollEvent event) {
double newZoomFactor = event.getDeltaY() > 0 ? 1.20 : 1 / 1.05;
private synchronized void guiZoom(ScrollEvent event) {
double newZoomFactor = event.getDeltaY() > 0 ? 1.10 : 1 / 1.05;
try {
// Get the old values
double oldScaleX = transform.getMxx();
double oldScaleY = transform.getMyy();
double oldTranslateX = transform.getTx();
double oldTranslateY = transform.getTy();
// Compute the new values
double newScaleX = oldScaleX * newZoomFactor;
double newScaleY = oldScaleY * newZoomFactor;
double newTranslateX = oldTranslateX - (event.getX() * (newScaleX - oldScaleX));
double newTranslateY = oldTranslateY - (event.getY() * (newScaleY - oldScaleY));
// Update the transform
transform.setMxx(newScaleX);
transform.setMyy(newScaleY);
transform.setTx(newTranslateX);
transform.setTy(newTranslateY);
this.zoomFactor *= newZoomFactor;
this.zoomLevel = (int) (Math.log(this.zoomFactor) / Math.log(4)); // Update zoom level.
System.out.println(zoomLevel);
// Set the actual centre coordinates (in the Cartesian plane). These are then used to update the calculations of fractal detail.
ChaosCanvas canvas = this.controller.getCanvas();
centreX = canvas.getMinCoords().getX0() + (event.getX() / getWidth()) * (canvas.getMaxCoords().getX0() - canvas.getMinCoords().getX0());
centreY = canvas.getMaxCoords().getX1() - (event.getY() / getHeight()) * (canvas.getMaxCoords().getX1() - canvas.getMinCoords().getX1());
this.updateController();
System.out.println(this.centreX + " " + this.centreY);
} catch (Exception e) {
PopupManager.displayError("Zoom error", e.getMessage());
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment