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]
main
David Wu 9 years ago
parent 26f7b94e13
commit af45be1147

@ -25,12 +25,12 @@ public class Constants {
public static final GPSCoordinate finishLineMarker2 = new GPSCoordinate(32.317257, -64.836260); public static final GPSCoordinate finishLineMarker2 = new GPSCoordinate(32.317257, -64.836260);
public static final BoatInRace[] OFFICIAL_AC35_COMPETITORS = new BoatInRace[] public static final BoatInRace[] OFFICIAL_AC35_COMPETITORS = new BoatInRace[]
{new BoatInRace("Oracle Team USA", 200.0, Color.BLUEVIOLET, "USA"), {new BoatInRace("Oracle Team USA", 700.0, Color.BLUEVIOLET, "USA"),
new BoatInRace("Land Rover BAR", 180.0, Color.BLACK, "BAR"), new BoatInRace("Land Rover BAR", 680.0, Color.BLACK, "BAR"),
new BoatInRace("SoftBank Team Japan", 190.0, Color.RED, "JAP"), new BoatInRace("SoftBank Team Japan", 690.0, Color.RED, "JAP"),
new BoatInRace("Groupama Team France", 210.0, Color.ORANGE, "FRN"), new BoatInRace("Groupama Team France", 710.0, Color.ORANGE, "FRN"),
new BoatInRace("Artemis Racing", 220.0, Color.DARKOLIVEGREEN, "ART"), new BoatInRace("Artemis Racing", 720.0, Color.DARKOLIVEGREEN, "ART"),
new BoatInRace("Emirates Team New Zealand", 310, Color.LIMEGREEN, "ENZ")}; new BoatInRace("Emirates Team New Zealand", 810, Color.LIMEGREEN, "ENZ")};
//public static final Leg bermudaCourseStartToMark1 = new Leg(0, , new ) //public static final Leg bermudaCourseStartToMark1 = new Leg(0, , new )
} }

@ -5,13 +5,17 @@ import javafx.beans.property.ReadOnlyObjectWrapper;
import javafx.beans.value.ObservableValue; import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections; import javafx.collections.FXCollections;
import javafx.collections.ObservableList; import javafx.collections.ObservableList;
import javafx.event.EventHandler;
import javafx.fxml.FXML; import javafx.fxml.FXML;
import javafx.scene.canvas.GraphicsContext; import javafx.scene.canvas.GraphicsContext;
import javafx.scene.control.Label; import javafx.scene.control.Label;
import javafx.scene.control.TableColumn; import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView; import javafx.scene.control.TableView;
import javafx.scene.control.TitledPane;
import javafx.scene.control.cell.PropertyValueFactory; import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.AnchorPane; import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.HBox;
import javafx.scene.paint.Color; import javafx.scene.paint.Color;
import javafx.util.Callback; import javafx.util.Callback;
import org.geotools.referencing.GeodeticCalculator; import org.geotools.referencing.GeodeticCalculator;
@ -38,6 +42,10 @@ public class RaceController extends Controller{
@FXML @FXML
Label timer; Label timer;
@FXML
Label FPS;
@FXML @FXML
TableView<BoatInRace> boatInfoTable; TableView<BoatInRace> boatInfoTable;
@FXML @FXML
@ -54,7 +62,6 @@ public class RaceController extends Controller{
* @param boats boats that are to be displayed in the race * @param boats boats that are to be displayed in the race
* @see ResizableRaceCanvas * @see ResizableRaceCanvas
*/ */
public void updateMap(ObservableList<BoatInRace> boats) { public void updateMap(ObservableList<BoatInRace> boats) {
BoatInRace[] boatInRaces = new BoatInRace[boats.size()]; BoatInRace[] boatInRaces = new BoatInRace[boats.size()];
raceMap.setBoats(boats.toArray(boatInRaces)); raceMap.setBoats(boats.toArray(boatInRaces));
@ -96,9 +103,10 @@ public class RaceController extends Controller{
ArrayList<Leg> legs = generateBermudaCourseLegs(); ArrayList<Leg> legs = generateBermudaCourseLegs();
ConstantVelocityRace race = new ConstantVelocityRace(boats, legs, this); ConstantVelocityRace race = new ConstantVelocityRace(boats, legs, this);
(new Thread(race)).start(); new Thread((race)).start();
} }
/** /**
* Function for the Bermuda Race. * Function for the Bermuda Race.
* @return legs in the Bermuda Race. * @return legs in the Bermuda Race.
@ -134,5 +142,7 @@ public class RaceController extends Controller{
timer.setText(time); timer.setText(time);
} }
public void setFrames(String fps) { FPS.setText((fps)); }
} }

@ -23,7 +23,7 @@ public class Boat {
*/ */
public Boat(String name, double velocity, String abbrev){ public Boat(String name, double velocity, String abbrev){
this.velocity = velocity; this.velocity = velocity;
this.velocityProp = new SimpleStringProperty(String.valueOf(velocity)); this.velocityProp = new SimpleStringProperty(String.valueOf(velocity* 1.94384));
this.abbrev = abbrev; this.abbrev = abbrev;
this.name = new SimpleStringProperty(name); this.name = new SimpleStringProperty(name);
} }

@ -1,6 +1,7 @@
package seng302.Model; package seng302.Model;
import javafx.animation.AnimationTimer;
import javafx.application.Platform; import javafx.application.Platform;
import javafx.beans.property.StringProperty; import javafx.beans.property.StringProperty;
import javafx.collections.FXCollections; import javafx.collections.FXCollections;
@ -24,7 +25,7 @@ public abstract class Race implements Runnable {
protected long totalTimeElapsed; 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 int PRERACE_TIME = 10000;//Integer.MAX_VALUE; //time in milliseconds to pause during pre-race
private boolean timerEnabled = true; private boolean timerEnabled = true;
@ -59,6 +60,9 @@ public abstract class Race implements Runnable {
simulateRace(); simulateRace();
} }
/**
* Disable the timer
*/
public void disableTimer() { public void disableTimer() {
timerEnabled = false; timerEnabled = false;
} }
@ -72,14 +76,16 @@ public abstract class Race implements Runnable {
System.out.println("===================="); System.out.println("====================");
for (int i = 0; i < startingBoats.size(); i++) { for (int i = 0; i < startingBoats.size(); i++) {
if (startingBoats.get(i) != null) { 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"); + Math.round(startingBoats.get(i).getVelocity() * 1.94384) + "kn");
startingBoats.get(i).setCurrentLeg(legs.get(0)); startingBoats.get(i).setCurrentLeg(legs.get(0));
} }
} }
} }
/**
* Countdown timer until race starts. Use PRERACE_TIME to set countdown duration.
*/
private void countdownTimer() { private void countdownTimer() {
long currentTime = System.currentTimeMillis(); long currentTime = System.currentTimeMillis();
long startTime = currentTime + PRERACE_TIME; 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() { private String calcTimer() {
long minutes; long minutes;
long currentTimeInSeconds; long currentTimeInSeconds;
@ -122,44 +132,73 @@ public abstract class Race implements Runnable {
return String.format("Race clock: %02d:%02d:%02d", hours, minutes, remainingSeconds); 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){ private void updateTime(String time){
Platform.runLater(() -> {controller.setTimer(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. * 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. * This prints the boats participating, the order that the events occur in time order, and the respective information of the events.
*/ */
private void simulateRace() { private void simulateRace() {
long timeRaceStarted = System.currentTimeMillis(); System.setProperty("javafx.animation.fullspeed", "true");
long timeLoopStarted; new AnimationTimer() {
long timeLoopEnded;
long timeRaceStarted = System.currentTimeMillis();
int fps = 0;
long timeCurrent = System.currentTimeMillis();
while (boatsFinished < startingBoats.size()) { @Override
timeLoopStarted = System.currentTimeMillis(); public void handle(long arg0) {
totalTimeElapsed = System.currentTimeMillis() - timeRaceStarted;
/*long timeLoopStarted;
long timeLoopEnded;
int fps = 0;*/
for (BoatInRace boat : startingBoats) { if (boatsFinished < startingBoats.size()) {
if (boat != null && !boat.isFinished()) { //timeLoopStarted = System.currentTimeMillis();
updatePosition(boat, SLEEP_TIME); totalTimeElapsed = System.currentTimeMillis() - timeRaceStarted;
checkPosition(boat, totalTimeElapsed);
}
}
if(controller != null) controller.updateMap(startingBoats); for (BoatInRace boat : startingBoats) {
if(timerEnabled) updateTime(calcTimer()); if (boat != null && !boat.isFinished()) {
try { updatePosition(boat, SLEEP_TIME);
timeLoopEnded = System.currentTimeMillis(); checkPosition(boat, totalTimeElapsed);
Thread.sleep(SLEEP_TIME - (timeLoopEnded - timeLoopStarted)); }
} catch (InterruptedException e) { }
return;
} 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();
} }
/** /**

@ -11,6 +11,8 @@ import seng302.GPSCoordinate;
import seng302.GraphCoordinate; import seng302.GraphCoordinate;
import seng302.RaceMap; import seng302.RaceMap;
import java.awt.*;
import java.awt.geom.Rectangle2D;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Random; import java.util.Random;
@ -23,6 +25,8 @@ public class ResizableRaceCanvas extends Canvas {
private GraphicsContext gc; private GraphicsContext gc;
private RaceMap map; private RaceMap map;
private BoatInRace[] boats; private BoatInRace[] boats;
private Graphics graphics;
private Font font;
/** /**
* Sets the boats that are to be displayed in this race. * 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){ public void displayArrow(GraphCoordinate coordinate, int angle){
gc.save(); gc.save();
rotate(angle, coordinate.getX(),coordinate.getY()); 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}, 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}, new double[]{coordinate.getY()-5, coordinate.getY()-20, coordinate.getY()-5, coordinate.getY()-5, coordinate.getY()+20, coordinate.getY()+20, coordinate.getY()-5},
7); 7);
@ -131,8 +136,17 @@ public class ResizableRaceCanvas extends Canvas {
* @param coordinate coordinate the text appears * @param coordinate coordinate the text appears
*/ */
public void displayText(String name, double speed, GraphCoordinate coordinate){ public void displayText(String name, double speed, GraphCoordinate coordinate){
String text = name + ", " + speed + " knots"; String text = String.format("%s, %2$.2f knots", name, speed);
gc.fillText(text, coordinate.getX()+20, coordinate.getY()); //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() { 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){ double width = getWidth();
return; 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 if (map == null){
gc.setLineWidth(2); return;
GraphCoordinate finishLineCoord1 = this.map.convertGPS(Constants.finishLineMarker1); }
GraphCoordinate finishLineCoord2 = this.map.convertGPS(Constants.finishLineMarker2);
displayLine(finishLineCoord1, finishLineCoord2, Color.DARKRED); //finish line
//marks gc.setLineWidth(2);
GraphCoordinate markCoord = this.map.convertGPS(Constants.mark1); GraphCoordinate finishLineCoord1 = this.map.convertGPS(Constants.finishLineMarker1);
GraphCoordinate windwardGate1 = this.map.convertGPS(Constants.windwardGate1); GraphCoordinate finishLineCoord2 = this.map.convertGPS(Constants.finishLineMarker2);
GraphCoordinate windwardGate2 = this.map.convertGPS(Constants.windwardGate2); displayLine(finishLineCoord1, finishLineCoord2, Color.DARKRED);
GraphCoordinate leewardGate1 = this.map.convertGPS(Constants.leewardGate1); //marks
GraphCoordinate leewardGate2 = this.map.convertGPS(Constants.leewardGate2); GraphCoordinate markCoord = this.map.convertGPS(Constants.mark1);
displayMark(markCoord, Color.GOLD); GraphCoordinate windwardGate1 = this.map.convertGPS(Constants.windwardGate1);
displayLine(windwardGate1, windwardGate2, Color.DARKCYAN); GraphCoordinate windwardGate2 = this.map.convertGPS(Constants.windwardGate2);
displayLine(leewardGate1, leewardGate2, Color.DARKVIOLET); GraphCoordinate leewardGate1 = this.map.convertGPS(Constants.leewardGate1);
//start line GraphCoordinate leewardGate2 = this.map.convertGPS(Constants.leewardGate2);
GraphCoordinate startline1 = this.map.convertGPS(Constants.startLineMarker1); displayMark(markCoord, Color.GOLD);
GraphCoordinate startline2 = this.map.convertGPS(Constants.startLineMarker2); displayLine(windwardGate1, windwardGate2, Color.DARKCYAN);
displayLine(leewardGate1, leewardGate2, Color.DARKVIOLET);
displayLine(startline1, startline2, Color.GREEN); //start line
GraphCoordinate startline1 = this.map.convertGPS(Constants.startLineMarker1);
GraphCoordinate startline2 = this.map.convertGPS(Constants.startLineMarker2);
if (boats != null) {
for (BoatInRace boat : boats) { displayLine(startline1, startline2, Color.GREEN);
if (boat != null) {
// System.out.print("Drawing Boat At: " + boat.getCurrentPosition()); if (boats != null) {
displayMark(this.map.convertGPS(boat.getCurrentPosition()), boat.getColour()); for (BoatInRace boat : boats) {
displayText(boat.getAbbrev(), boat.getVelocity(), this.map.convertGPS(boat.getCurrentPosition())); 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 //display wind direction arrow - specify origin point and angle
displayArrow(new GraphCoordinate(500, 20), 100); displayArrow(new GraphCoordinate(500, 20), 100);
} }
/** /**

@ -12,9 +12,11 @@
<AnchorPane fx:id="canvasBase"> <AnchorPane fx:id="canvasBase">
<children> <children>
<Label fx:id="timer" layoutX="45.0" layoutY="146.0" text="0:0" AnchorPane.bottomAnchor="0.0" AnchorPane.rightAnchor="0.0" /> <Label fx:id="timer" layoutX="45.0" layoutY="146.0" text="0:0" AnchorPane.bottomAnchor="0.0" AnchorPane.rightAnchor="0.0" />
<Label fx:id="FPS" layoutX="-20.0" layoutY="153.0" text="FPS: 0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="-0.0" />
</children></AnchorPane> </children></AnchorPane>
<AnchorPane layoutX="450.0" minHeight="0.0" minWidth="0.0" prefHeight="160.0" prefWidth="100.0" GridPane.columnIndex="1"> <AnchorPane layoutX="450.0" minHeight="0.0" minWidth="0.0" prefHeight="160.0" prefWidth="100.0" GridPane.columnIndex="1">
<children> <children>
<TableView fx:id="boatInfoTable" prefHeight="400.0" prefWidth="146.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0"> <TableView fx:id="boatInfoTable" prefHeight="400.0" prefWidth="146.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
<columns> <columns>
<TableColumn fx:id="boatPlacingColumn" prefWidth="50.0" text="Place" /> <TableColumn fx:id="boatPlacingColumn" prefWidth="50.0" text="Place" />

Loading…
Cancel
Save