From af45be1147360804d117c6bac74b420d45c00037 Mon Sep 17 00:00:00 2001 From: David Wu Date: Thu, 23 Mar 2017 18:11:13 +1300 Subject: [PATCH 1/6] Implemented Animation Timer, FPS counter and modified display boat names and speed -Race now uses Animation Timer to simulate the race in Race class -FPS counter displayed in bottom left corner. TODO: Toggle fps view -Boat names and speed will now reposition so it is not blocked by the border of the canvas #story [18, 20] --- src/main/java/seng302/Constants.java | 12 +-- .../seng302/Controllers/RaceController.java | 14 ++- src/main/java/seng302/Model/Boat.java | 2 +- src/main/java/seng302/Model/Race.java | 89 ++++++++++++----- .../seng302/Model/ResizableRaceCanvas.java | 95 +++++++++++-------- src/main/resources/scenes/racepane.fxml | 2 + 6 files changed, 140 insertions(+), 74 deletions(-) diff --git a/src/main/java/seng302/Constants.java b/src/main/java/seng302/Constants.java index e7569a13..1cbf78ab 100644 --- a/src/main/java/seng302/Constants.java +++ b/src/main/java/seng302/Constants.java @@ -25,12 +25,12 @@ public class Constants { public static final GPSCoordinate finishLineMarker2 = new GPSCoordinate(32.317257, -64.836260); public static final BoatInRace[] OFFICIAL_AC35_COMPETITORS = new BoatInRace[] - {new BoatInRace("Oracle Team USA", 200.0, Color.BLUEVIOLET, "USA"), - new BoatInRace("Land Rover BAR", 180.0, Color.BLACK, "BAR"), - new BoatInRace("SoftBank Team Japan", 190.0, Color.RED, "JAP"), - new BoatInRace("Groupama Team France", 210.0, Color.ORANGE, "FRN"), - new BoatInRace("Artemis Racing", 220.0, Color.DARKOLIVEGREEN, "ART"), - new BoatInRace("Emirates Team New Zealand", 310, Color.LIMEGREEN, "ENZ")}; + {new BoatInRace("Oracle Team USA", 700.0, Color.BLUEVIOLET, "USA"), + new BoatInRace("Land Rover BAR", 680.0, Color.BLACK, "BAR"), + new BoatInRace("SoftBank Team Japan", 690.0, Color.RED, "JAP"), + new BoatInRace("Groupama Team France", 710.0, Color.ORANGE, "FRN"), + new BoatInRace("Artemis Racing", 720.0, Color.DARKOLIVEGREEN, "ART"), + new BoatInRace("Emirates Team New Zealand", 810, Color.LIMEGREEN, "ENZ")}; //public static final Leg bermudaCourseStartToMark1 = new Leg(0, , new ) } diff --git a/src/main/java/seng302/Controllers/RaceController.java b/src/main/java/seng302/Controllers/RaceController.java index 317d69da..f9e5c63a 100644 --- a/src/main/java/seng302/Controllers/RaceController.java +++ b/src/main/java/seng302/Controllers/RaceController.java @@ -5,13 +5,17 @@ import javafx.beans.property.ReadOnlyObjectWrapper; import javafx.beans.value.ObservableValue; import javafx.collections.FXCollections; import javafx.collections.ObservableList; +import javafx.event.EventHandler; import javafx.fxml.FXML; import javafx.scene.canvas.GraphicsContext; import javafx.scene.control.Label; import javafx.scene.control.TableColumn; import javafx.scene.control.TableView; +import javafx.scene.control.TitledPane; import javafx.scene.control.cell.PropertyValueFactory; +import javafx.scene.input.MouseEvent; import javafx.scene.layout.AnchorPane; +import javafx.scene.layout.HBox; import javafx.scene.paint.Color; import javafx.util.Callback; import org.geotools.referencing.GeodeticCalculator; @@ -38,6 +42,10 @@ public class RaceController extends Controller{ @FXML Label timer; + @FXML + Label FPS; + + @FXML TableView boatInfoTable; @FXML @@ -54,7 +62,6 @@ public class RaceController extends Controller{ * @param boats boats that are to be displayed in the race * @see ResizableRaceCanvas */ - public void updateMap(ObservableList boats) { BoatInRace[] boatInRaces = new BoatInRace[boats.size()]; raceMap.setBoats(boats.toArray(boatInRaces)); @@ -96,9 +103,10 @@ public class RaceController extends Controller{ ArrayList legs = generateBermudaCourseLegs(); ConstantVelocityRace race = new ConstantVelocityRace(boats, legs, this); - (new Thread(race)).start(); + new Thread((race)).start(); } + /** * Function for the Bermuda Race. * @return legs in the Bermuda Race. @@ -134,5 +142,7 @@ public class RaceController extends Controller{ timer.setText(time); } + public void setFrames(String fps) { FPS.setText((fps)); } + } diff --git a/src/main/java/seng302/Model/Boat.java b/src/main/java/seng302/Model/Boat.java index 4cd2503b..8ad15927 100644 --- a/src/main/java/seng302/Model/Boat.java +++ b/src/main/java/seng302/Model/Boat.java @@ -23,7 +23,7 @@ public class Boat { */ public Boat(String name, double velocity, String abbrev){ this.velocity = velocity; - this.velocityProp = new SimpleStringProperty(String.valueOf(velocity)); + this.velocityProp = new SimpleStringProperty(String.valueOf(velocity* 1.94384)); this.abbrev = abbrev; this.name = new SimpleStringProperty(name); } diff --git a/src/main/java/seng302/Model/Race.java b/src/main/java/seng302/Model/Race.java index 97c675f5..a2e2df1f 100644 --- a/src/main/java/seng302/Model/Race.java +++ b/src/main/java/seng302/Model/Race.java @@ -1,6 +1,7 @@ package seng302.Model; +import javafx.animation.AnimationTimer; import javafx.application.Platform; import javafx.beans.property.StringProperty; import javafx.collections.FXCollections; @@ -24,7 +25,7 @@ public abstract class Race implements Runnable { protected long totalTimeElapsed; - private int SLEEP_TIME = 50; //time in milliseconds to pause in a paced loop + private int SLEEP_TIME = 25; //time in milliseconds to pause in a paced loop private int PRERACE_TIME = 10000;//Integer.MAX_VALUE; //time in milliseconds to pause during pre-race private boolean timerEnabled = true; @@ -59,6 +60,9 @@ public abstract class Race implements Runnable { simulateRace(); } + /** + * Disable the timer + */ public void disableTimer() { timerEnabled = false; } @@ -72,14 +76,16 @@ public abstract class Race implements Runnable { System.out.println("===================="); for (int i = 0; i < startingBoats.size(); i++) { if (startingBoats.get(i) != null) { - System.out.println(i + 1 + ". " + startingBoats.get(i).getName() + ", Speed: " + System.out.println(i + 1 + ". " + startingBoats.get(i).toString() + ", Speed: " + Math.round(startingBoats.get(i).getVelocity() * 1.94384) + "kn"); startingBoats.get(i).setCurrentLeg(legs.get(0)); } } } - + /** + * Countdown timer until race starts. Use PRERACE_TIME to set countdown duration. + */ private void countdownTimer() { long currentTime = System.currentTimeMillis(); long startTime = currentTime + PRERACE_TIME; @@ -108,6 +114,10 @@ public abstract class Race implements Runnable { } } + /** + * Takes total time elapsed and format to hour:minute:second + * @return Formatted time as string + */ private String calcTimer() { long minutes; long currentTimeInSeconds; @@ -122,44 +132,73 @@ public abstract class Race implements Runnable { return String.format("Race clock: %02d:%02d:%02d", hours, minutes, remainingSeconds); } + /** + * Updates the calculated time to the timer label + * @param time The calculated time from calcTimer() method + */ private void updateTime(String time){ - Platform.runLater(() -> {controller.setTimer(time);}); } + private void updateFPS(int fps) { + Platform.runLater(() -> {controller.setFrames("FPS: " + fps);}); + } + /** * Starts the Race Simulation, playing the race start to finish with the timescale. * This prints the boats participating, the order that the events occur in time order, and the respective information of the events. */ private void simulateRace() { - long timeRaceStarted = System.currentTimeMillis(); + System.setProperty("javafx.animation.fullspeed", "true"); - long timeLoopStarted; - long timeLoopEnded; + new AnimationTimer() { + long timeRaceStarted = System.currentTimeMillis(); + int fps = 0; + long timeCurrent = System.currentTimeMillis(); - while (boatsFinished < startingBoats.size()) { - timeLoopStarted = System.currentTimeMillis(); - totalTimeElapsed = System.currentTimeMillis() - timeRaceStarted; + @Override + public void handle(long arg0) { + /*long timeLoopStarted; + long timeLoopEnded; + int fps = 0;*/ - for (BoatInRace boat : startingBoats) { - if (boat != null && !boat.isFinished()) { - updatePosition(boat, SLEEP_TIME); - checkPosition(boat, totalTimeElapsed); - } - } + if (boatsFinished < startingBoats.size()) { + //timeLoopStarted = System.currentTimeMillis(); + totalTimeElapsed = System.currentTimeMillis() - timeRaceStarted; - if(controller != null) controller.updateMap(startingBoats); - if(timerEnabled) updateTime(calcTimer()); - try { - timeLoopEnded = System.currentTimeMillis(); - Thread.sleep(SLEEP_TIME - (timeLoopEnded - timeLoopStarted)); - } catch (InterruptedException e) { - return; - } - } + for (BoatInRace boat : startingBoats) { + if (boat != null && !boat.isFinished()) { + updatePosition(boat, SLEEP_TIME); + checkPosition(boat, totalTimeElapsed); + } + } + + if (controller != null) controller.updateMap(startingBoats); + if (timerEnabled) + updateTime(calcTimer()); + } + fps++; + if ((System.currentTimeMillis()-timeCurrent) > 1000){ + updateFPS(fps); + fps = 0; + timeCurrent = System.currentTimeMillis(); + } + ; + + + /*fps++; + try { + timeLoopEnded = System.currentTimeMillis(); + Thread.sleep(SLEEP_TIME - (timeLoopEnded - timeLoopStarted)); + } catch (InterruptedException e) { + return; + }*/ + }; + //System.out.println("Avg fps:" + fps/(totalTimeElapsed/1000)); + }.start(); } /** diff --git a/src/main/java/seng302/Model/ResizableRaceCanvas.java b/src/main/java/seng302/Model/ResizableRaceCanvas.java index bcb44e1e..ad0c9ec2 100644 --- a/src/main/java/seng302/Model/ResizableRaceCanvas.java +++ b/src/main/java/seng302/Model/ResizableRaceCanvas.java @@ -11,6 +11,8 @@ import seng302.GPSCoordinate; import seng302.GraphCoordinate; import seng302.RaceMap; +import java.awt.*; +import java.awt.geom.Rectangle2D; import java.util.ArrayList; import java.util.Random; @@ -23,6 +25,8 @@ public class ResizableRaceCanvas extends Canvas { private GraphicsContext gc; private RaceMap map; private BoatInRace[] boats; + private Graphics graphics; + private Font font; /** * Sets the boats that are to be displayed in this race. @@ -107,6 +111,7 @@ public class ResizableRaceCanvas extends Canvas { public void displayArrow(GraphCoordinate coordinate, int angle){ gc.save(); rotate(angle, coordinate.getX(),coordinate.getY()); + gc.setFill(Color.BLACK); gc.fillPolygon(new double[]{coordinate.getX()-12, coordinate.getX()-6, coordinate.getX(), coordinate.getX()-4, coordinate.getX()-4, coordinate.getX()-8, coordinate.getX()-8}, new double[]{coordinate.getY()-5, coordinate.getY()-20, coordinate.getY()-5, coordinate.getY()-5, coordinate.getY()+20, coordinate.getY()+20, coordinate.getY()-5}, 7); @@ -131,8 +136,17 @@ public class ResizableRaceCanvas extends Canvas { * @param coordinate coordinate the text appears */ public void displayText(String name, double speed, GraphCoordinate coordinate){ - String text = name + ", " + speed + " knots"; - gc.fillText(text, coordinate.getX()+20, coordinate.getY()); + String text = String.format("%s, %2$.2f knots", name, speed); + //System.out.println(text.length()*7); + long xCoord = coordinate.getX()+20; + long yCoord = coordinate.getY(); + if (xCoord+(text.length()*7) >= getWidth()){ + xCoord -= text.length()*7; + } + if (yCoord-(text.length()*2) <= 0){ + yCoord += 30; + } + gc.fillText(text, xCoord, yCoord); } /** @@ -140,50 +154,51 @@ public class ResizableRaceCanvas extends Canvas { */ public void drawRaceMap() { - double width = getWidth(); - double height = getHeight(); - gc.clearRect(0, 0, width, height); - //System.out.println("Race Map Canvas Width: "+ width + ", Height:" + height); - this.map = new RaceMap(32.278, -64.863, 32.320989, -64.821, (int)width, (int)height); - if (map == null){ - return; - } + double width = getWidth(); + double height = getHeight(); + + gc.clearRect(0, 0, width, height); + //System.out.println("Race Map Canvas Width: "+ width + ", Height:" + height); + this.map = new RaceMap(32.278, -64.863, 32.320989, -64.821, (int)width, (int)height); - //finish line - gc.setLineWidth(2); - GraphCoordinate finishLineCoord1 = this.map.convertGPS(Constants.finishLineMarker1); - GraphCoordinate finishLineCoord2 = this.map.convertGPS(Constants.finishLineMarker2); - displayLine(finishLineCoord1, finishLineCoord2, Color.DARKRED); - //marks - GraphCoordinate markCoord = this.map.convertGPS(Constants.mark1); - GraphCoordinate windwardGate1 = this.map.convertGPS(Constants.windwardGate1); - GraphCoordinate windwardGate2 = this.map.convertGPS(Constants.windwardGate2); - GraphCoordinate leewardGate1 = this.map.convertGPS(Constants.leewardGate1); - GraphCoordinate leewardGate2 = this.map.convertGPS(Constants.leewardGate2); - displayMark(markCoord, Color.GOLD); - displayLine(windwardGate1, windwardGate2, Color.DARKCYAN); - displayLine(leewardGate1, leewardGate2, Color.DARKVIOLET); - //start line - GraphCoordinate startline1 = this.map.convertGPS(Constants.startLineMarker1); - GraphCoordinate startline2 = this.map.convertGPS(Constants.startLineMarker2); - - displayLine(startline1, startline2, Color.GREEN); - - - if (boats != null) { - for (BoatInRace boat : boats) { - if (boat != null) { -// System.out.print("Drawing Boat At: " + boat.getCurrentPosition()); - displayMark(this.map.convertGPS(boat.getCurrentPosition()), boat.getColour()); - displayText(boat.getAbbrev(), boat.getVelocity(), this.map.convertGPS(boat.getCurrentPosition())); + if (map == null){ + return; + } + + //finish line + gc.setLineWidth(2); + GraphCoordinate finishLineCoord1 = this.map.convertGPS(Constants.finishLineMarker1); + GraphCoordinate finishLineCoord2 = this.map.convertGPS(Constants.finishLineMarker2); + displayLine(finishLineCoord1, finishLineCoord2, Color.DARKRED); + //marks + GraphCoordinate markCoord = this.map.convertGPS(Constants.mark1); + GraphCoordinate windwardGate1 = this.map.convertGPS(Constants.windwardGate1); + GraphCoordinate windwardGate2 = this.map.convertGPS(Constants.windwardGate2); + GraphCoordinate leewardGate1 = this.map.convertGPS(Constants.leewardGate1); + GraphCoordinate leewardGate2 = this.map.convertGPS(Constants.leewardGate2); + displayMark(markCoord, Color.GOLD); + displayLine(windwardGate1, windwardGate2, Color.DARKCYAN); + displayLine(leewardGate1, leewardGate2, Color.DARKVIOLET); + //start line + GraphCoordinate startline1 = this.map.convertGPS(Constants.startLineMarker1); + GraphCoordinate startline2 = this.map.convertGPS(Constants.startLineMarker2); + + displayLine(startline1, startline2, Color.GREEN); + + if (boats != null) { + for (BoatInRace boat : boats) { + if (boat != null) { + // System.out.print("Drawing Boat At: " + boat.getCurrentPosition()); + displayMark(this.map.convertGPS(boat.getCurrentPosition()), boat.getColour()); + displayText(boat.getName().getValue(), boat.getVelocity()* 1.94384, this.map.convertGPS(boat.getCurrentPosition())); + } } } - } - //display wind direction arrow - specify origin point and angle - displayArrow(new GraphCoordinate(500, 20), 100); + //display wind direction arrow - specify origin point and angle + displayArrow(new GraphCoordinate(500, 20), 100); } /** diff --git a/src/main/resources/scenes/racepane.fxml b/src/main/resources/scenes/racepane.fxml index ed538a38..61e3c713 100644 --- a/src/main/resources/scenes/racepane.fxml +++ b/src/main/resources/scenes/racepane.fxml @@ -12,9 +12,11 @@ + From 0df8235a28c45ac053dd04b70011f8a00031bc60 Mon Sep 17 00:00:00 2001 From: David Wu Date: Sun, 26 Mar 2017 17:40:09 +1300 Subject: [PATCH 2/6] Added TitledPane and Checkbox to GUI. -Checkbox is used to control the visibility of the FPS counter #story [20] --- .../seng302/Controllers/RaceController.java | 23 ++++- .../seng302/Model/ResizableRaceCanvas.java | 3 +- src/main/resources/scenes/racepane.fxml | 92 +++++++++---------- 3 files changed, 66 insertions(+), 52 deletions(-) diff --git a/src/main/java/seng302/Controllers/RaceController.java b/src/main/java/seng302/Controllers/RaceController.java index fcaf8dec..fc7baceb 100644 --- a/src/main/java/seng302/Controllers/RaceController.java +++ b/src/main/java/seng302/Controllers/RaceController.java @@ -2,15 +2,12 @@ package seng302.Controllers; import javafx.beans.property.ReadOnlyObjectWrapper; +import javafx.beans.value.ChangeListener; import javafx.beans.value.ObservableValue; import javafx.collections.ObservableList; import javafx.event.EventHandler; import javafx.fxml.FXML; -import javafx.scene.control.Label; -import javafx.scene.control.SplitPane; -import javafx.scene.control.TableColumn; -import javafx.scene.control.TableView; -import javafx.scene.control.TitledPane; +import javafx.scene.control.*; import javafx.scene.control.cell.PropertyValueFactory; import javafx.scene.input.MouseEvent; import javafx.scene.layout.AnchorPane; @@ -39,6 +36,9 @@ public class RaceController extends Controller{ @FXML SplitPane ongoingRacePane; + @FXML + CheckBox showFPS; + @FXML Label timer; @@ -116,6 +116,7 @@ public class RaceController extends Controller{ } + /** * Initializes and runs the race, based on the user's chosen scale factor * Currently uses an example racecourse @@ -125,6 +126,7 @@ public class RaceController extends Controller{ private void startRace(int scaleFactor) { BoatInRace[] boats = generateAC35Competitors(); raceMap = new ResizableRaceCanvas(); + raceMap.setMouseTransparent(true); raceMap.widthProperty().bind(canvasBase.widthProperty()); raceMap.heightProperty().bind(canvasBase.heightProperty()); raceMap.setBoats(boats); @@ -138,6 +140,17 @@ public class RaceController extends Controller{ ArrayList legs = generateBermudaCourseLegs(); ConstantVelocityRace race = new ConstantVelocityRace(boats, legs, this, scaleFactor); + showFPS.setVisible(true); + showFPS.selectedProperty().addListener(new ChangeListener() { + public void changed(ObservableValue ov, + Boolean old_val, Boolean new_val) { + if (showFPS.isSelected()){ + FPS.setVisible(true); + } else { + FPS.setVisible(false); + } + } + }); new Thread((race)).start(); } diff --git a/src/main/java/seng302/Model/ResizableRaceCanvas.java b/src/main/java/seng302/Model/ResizableRaceCanvas.java index fb9b65c9..f5be9774 100644 --- a/src/main/java/seng302/Model/ResizableRaceCanvas.java +++ b/src/main/java/seng302/Model/ResizableRaceCanvas.java @@ -35,6 +35,7 @@ public class ResizableRaceCanvas extends Canvas { this.boats = boats; } + public ResizableRaceCanvas(RaceMap map) { this.map = map; gc = this.getGraphicsContext2D(); @@ -160,7 +161,6 @@ public class ResizableRaceCanvas extends Canvas { gc.clearRect(0, 0, width, height); //System.out.println("Race Map Canvas Width: "+ width + ", Height:" + height); this.map = new RaceMap(32.278, -64.863, 32.320989, -64.821, (int) width, (int) height); - if (map == null) { return; } @@ -244,4 +244,5 @@ public class ResizableRaceCanvas extends Canvas { public double prefHeight(double height) { return getHeight(); } + } diff --git a/src/main/resources/scenes/racepane.fxml b/src/main/resources/scenes/racepane.fxml index 84b21769..34f6d10b 100644 --- a/src/main/resources/scenes/racepane.fxml +++ b/src/main/resources/scenes/racepane.fxml @@ -1,89 +1,89 @@ + + - + + - - - - - + + + + + - - - - - + + + + + - + - + -