Merge remote-tracking branch 'origin/heading-visualisation'
# Conflicts: # src/main/java/seng302/Controllers/RaceController.java # src/main/java/seng302/Model/Boat.java # src/main/java/seng302/Model/Race.java # src/main/java/seng302/Model/ResizableRaceCanvas.java # src/main/resources/scenes/racepane.fxml
This commit is contained in:
@@ -21,6 +21,8 @@ public class Constants {
|
||||
public static final GPSCoordinate finishLineMarker1 = new GPSCoordinate(32.317379, -64.839291);
|
||||
public static final GPSCoordinate finishLineMarker2 = new GPSCoordinate(32.317257, -64.836260);
|
||||
|
||||
public static final double wakeScale = 10;
|
||||
|
||||
public static final BoatInRace[] OFFICIAL_AC35_COMPETITORS = new BoatInRace[]
|
||||
{new BoatInRace("Oracle Team USA", 30.0, Color.BLUEVIOLET, "Oracle"),
|
||||
new BoatInRace("Land Rover BAR", 23.0, Color.BLACK, "BGR"),
|
||||
|
||||
@@ -73,7 +73,7 @@ public class RaceController extends Controller{
|
||||
public void updateMap(ObservableList<BoatInRace> boats) {
|
||||
BoatInRace[] boatInRaces = new BoatInRace[boats.size()];
|
||||
raceMap.setBoats(boats.toArray(boatInRaces));
|
||||
raceMap.drawRaceMap();
|
||||
raceMap.update();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -146,7 +146,6 @@ public class RaceController extends Controller{
|
||||
});*/
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Initializes and runs the race, based on the user's chosen scale factor
|
||||
* Currently uses an example racecourse
|
||||
@@ -173,6 +172,7 @@ public class RaceController extends Controller{
|
||||
raceMap.widthProperty().bind(canvasBase.widthProperty());
|
||||
raceMap.heightProperty().bind(canvasBase.heightProperty());
|
||||
raceMap.setBoats(boats);
|
||||
raceMap.drawBoats();
|
||||
raceMap.drawRaceMap();
|
||||
raceMap.setVisible(true);
|
||||
|
||||
@@ -192,7 +192,7 @@ public class RaceController extends Controller{
|
||||
public void changed(ObservableValue<? extends Boolean> ov,
|
||||
Boolean old_val, Boolean new_val) {
|
||||
raceMap.toggleAnno();
|
||||
raceMap.drawRaceMap();
|
||||
raceMap.update();
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -200,6 +200,7 @@ public class RaceController extends Controller{
|
||||
|
||||
/**
|
||||
* Function for the Bermuda Race.
|
||||
*
|
||||
* @return legs in the Bermuda Race.
|
||||
*/
|
||||
private ArrayList<Leg> generateBermudaCourseLegs() {
|
||||
@@ -249,5 +250,4 @@ public class RaceController extends Controller{
|
||||
*/
|
||||
public void setFrames(String fps) { FPS.setText((fps)); }
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -48,6 +48,15 @@ public class Boat {
|
||||
return velocity;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the speed of the boat in knots.
|
||||
* @param velocity speed in knots
|
||||
*/
|
||||
public void setVelocity(double velocity) {
|
||||
this.velocity = velocity;
|
||||
this.velocityProp.setValue(velocity);
|
||||
}
|
||||
|
||||
/**
|
||||
* Print method prints the name of the boat
|
||||
* @return Name of the boat.
|
||||
|
||||
@@ -4,8 +4,10 @@ import javafx.beans.property.SimpleStringProperty;
|
||||
import javafx.beans.property.StringProperty;
|
||||
import javafx.scene.paint.Color;
|
||||
import org.geotools.referencing.GeodeticCalculator;
|
||||
import seng302.Constants;
|
||||
import seng302.GPSCoordinate;
|
||||
|
||||
import java.awt.geom.Point2D;
|
||||
|
||||
/**
|
||||
* Boat in the Race extends Boat.
|
||||
@@ -73,6 +75,25 @@ public class BoatInRace extends Boat {
|
||||
return calculateHeading(azimuth);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the position of the end of the boat's wake, which is 180 degrees
|
||||
* from the boat's heading, and whose length is proportional to the boat's
|
||||
* speed.
|
||||
* @return GPSCoordinate of wake endpoint.
|
||||
*/
|
||||
public GPSCoordinate getWake() {
|
||||
double reverseHeading = calculateHeading() - 180;
|
||||
double distance = Constants.wakeScale * getVelocity();
|
||||
|
||||
GeodeticCalculator calc = new GeodeticCalculator();
|
||||
calc.setStartingGeographicPoint(
|
||||
new Point2D.Double(getCurrentPosition().getLongitude(), getCurrentPosition().getLatitude())
|
||||
);
|
||||
calc.setDirection(reverseHeading, distance);
|
||||
Point2D endpoint = calc.getDestinationGeographicPoint();
|
||||
return new GPSCoordinate(endpoint.getY(), endpoint.getX());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Scaled velocity of the boat
|
||||
*/
|
||||
|
||||
@@ -118,15 +118,18 @@ public abstract class Race implements Runnable {
|
||||
long timeLoopEnded;
|
||||
|
||||
while (currentTime <= startTime) {
|
||||
//if (controller != null) controller.updateMap(startingBoats);
|
||||
timeLeft = startTime - currentTime;
|
||||
currentTimeInSeconds = timeLeft / 1000;
|
||||
minutes = currentTimeInSeconds / 60;
|
||||
remainingSeconds = currentTimeInSeconds % 60;
|
||||
hours = minutes / 60;
|
||||
minutes = minutes % 60;
|
||||
if (controller != null) {
|
||||
updateTime(String.format("Time until race starts: %02d:%02d:%02d", hours, minutes, remainingSeconds));
|
||||
if (timeLeft == 0 && controller != null) {
|
||||
updateTime("Race is starting...");
|
||||
} else {
|
||||
currentTimeInSeconds = timeLeft / 1000;
|
||||
minutes = currentTimeInSeconds / 60;
|
||||
remainingSeconds = currentTimeInSeconds % 60;
|
||||
hours = minutes / 60;
|
||||
minutes = minutes % 60;
|
||||
if (controller != null) {
|
||||
updateTime(String.format("Race clock: -%02d:%02d:%02d", hours, minutes, remainingSeconds));
|
||||
}
|
||||
}
|
||||
try {
|
||||
timeLoopEnded = System.currentTimeMillis();
|
||||
@@ -162,6 +165,16 @@ public abstract class Race implements Runnable {
|
||||
*/
|
||||
protected void updateTime(String time){
|
||||
Platform.runLater(() -> {controller.setTimer(time);});
|
||||
=======
|
||||
* Updates the GUI race clock
|
||||
* @param time
|
||||
*/
|
||||
protected void updateTime(String time) {
|
||||
|
||||
Platform.runLater(() -> {
|
||||
controller.setTimer(time);
|
||||
});
|
||||
>>>>>>> Temporary merge branch 2
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -183,6 +196,8 @@ public abstract class Race implements Runnable {
|
||||
*/
|
||||
private void simulateRace() {
|
||||
|
||||
System.setProperty("javafx.animation.fullspeed", "true");
|
||||
|
||||
new AnimationTimer() {
|
||||
|
||||
long timeRaceStarted = System.currentTimeMillis(); //start time of loop
|
||||
@@ -242,7 +257,6 @@ public abstract class Race implements Runnable {
|
||||
*/
|
||||
protected void checkPosition(BoatInRace boat, long timeElapsed) {
|
||||
if (boat.getDistanceTravelledInLeg() > boat.getCurrentLeg().getDistance()){
|
||||
// updateController();
|
||||
//boat has passed onto new leg
|
||||
if (boat.getCurrentLeg().getName().equals("Finish")) {
|
||||
//boat has finished
|
||||
@@ -254,13 +268,17 @@ public abstract class Race implements Runnable {
|
||||
boat.setFinished(true);
|
||||
boat.setCurrentLeg(new Leg("DNF",-1));
|
||||
} else {
|
||||
//Calculate how much the boat overshot the marker by
|
||||
boat.setDistanceTravelledInLeg(boat.getDistanceTravelledInLeg() - boat.getCurrentLeg().getDistance());
|
||||
//Move boat on to next leg
|
||||
Leg nextLeg = legs.get(boat.getCurrentLeg().getLegNumber() + 1);
|
||||
|
||||
boat.setCurrentLeg(nextLeg);
|
||||
//Add overshoot distance into the distance travelled for the next leg
|
||||
boat.setDistanceTravelledInLeg(boat.getDistanceTravelledInLeg());
|
||||
}
|
||||
FXCollections.sort(startingBoats, (a,b) -> b.getCurrentLeg().getLegNumber() - a.getCurrentLeg().getLegNumber());
|
||||
//Update the boat display table in the GUI to reflect the leg change
|
||||
FXCollections.sort(startingBoats, (a, b) -> b.getCurrentLeg().getLegNumber() - a.getCurrentLeg().getLegNumber());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -282,9 +300,9 @@ public abstract class Race implements Runnable {
|
||||
}
|
||||
|
||||
/**
|
||||
* This function is a function that generates the Race and populates the events list.
|
||||
* Is automatically called by the initialiser function, so that simulateRace() does not return an empty race.
|
||||
* @see Race#simulateRace()
|
||||
* Updates the boat's gps coordinates depending on time elapsed
|
||||
* @param boat
|
||||
* @param millisecondsElapsed
|
||||
*/
|
||||
protected abstract void updatePosition(BoatInRace boat, int millisecondsElapsed);
|
||||
|
||||
|
||||
@@ -40,7 +40,6 @@ public class ResizableRaceCanvas extends Canvas {
|
||||
this.boats = boats;
|
||||
}
|
||||
|
||||
|
||||
public ResizableRaceCanvas(RaceMap map) {
|
||||
this.map = map;
|
||||
gc = this.getGraphicsContext2D();
|
||||
@@ -73,9 +72,24 @@ public class ResizableRaceCanvas extends Canvas {
|
||||
* @see Color
|
||||
* @see Paint
|
||||
*/
|
||||
private void displayMark(GraphCoordinate graphCoordinate, Paint paint) {
|
||||
public void displayMark(GraphCoordinate graphCoordinate, Paint paint){
|
||||
double d = 25;
|
||||
gc.setFill(paint);
|
||||
gc.fillOval(graphCoordinate.getX(), graphCoordinate.getY(), 25, 25);
|
||||
gc.fillOval(graphCoordinate.getX() - (d/2), graphCoordinate.getY() - (d/2), d, d);
|
||||
}
|
||||
|
||||
public void displayBoat(BoatInRace boat, double angle) {
|
||||
GraphCoordinate pos = this.map.convertGPS(boat.getCurrentPosition());
|
||||
Paint paint = boat.getColour();
|
||||
|
||||
double[] x = {pos.getX() - 6, pos.getX(), pos.getX() + 6};
|
||||
double[] y = {pos.getY() + 12, pos.getY() - 12, pos.getY() + 12};
|
||||
gc.setFill(paint);
|
||||
|
||||
gc.save();
|
||||
rotate(angle, pos.getX(), pos.getY());
|
||||
gc.fillPolygon(x, y, 3);
|
||||
gc.restore();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -117,10 +131,10 @@ public class ResizableRaceCanvas extends Canvas {
|
||||
*/
|
||||
private void displayArrow(GraphCoordinate coordinate, int angle) {
|
||||
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},
|
||||
new double[]{coordinate.getY()-5, coordinate.getY()-20, coordinate.getY()-5, coordinate.getY()-5, coordinate.getY()+20, coordinate.getY()+20, coordinate.getY()-5},
|
||||
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);
|
||||
gc.restore();
|
||||
}
|
||||
@@ -156,6 +170,13 @@ public class ResizableRaceCanvas extends Canvas {
|
||||
gc.fillText(text, xCoord, yCoord);
|
||||
}
|
||||
|
||||
/**
|
||||
* Draws race map with up to date data.
|
||||
*/
|
||||
public void update() {
|
||||
this.drawRaceMap();
|
||||
this.updateBoats();
|
||||
}
|
||||
|
||||
/**
|
||||
* Draws the Race Map
|
||||
@@ -168,9 +189,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;
|
||||
}
|
||||
|
||||
//finish line
|
||||
gc.setLineWidth(2);
|
||||
@@ -208,18 +226,39 @@ public class ResizableRaceCanvas extends Canvas {
|
||||
displayArrow(new GraphCoordinate((int)getWidth()-40, 40), 150);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Toggle the raceAnno value
|
||||
*/
|
||||
public void toggleAnno(){
|
||||
if (raceAnno){
|
||||
public void toggleAnno() {
|
||||
if (raceAnno) {
|
||||
raceAnno = false;
|
||||
} else {
|
||||
raceAnno = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Draws boats while race in progress, when leg heading is set.
|
||||
*/
|
||||
public void updateBoats() {
|
||||
if (boats != null) {
|
||||
for (BoatInRace boat : boats) {
|
||||
boolean finished = boat.getCurrentLeg().getName().equals("Finish") || boat.getCurrentLeg().getName().equals("DNF");
|
||||
if (!finished) {
|
||||
displayBoat(boat, boat.calculateHeading());
|
||||
GraphCoordinate wakeFrom = this.map.convertGPS(boat.getCurrentPosition());
|
||||
GraphCoordinate wakeTo = this.map.convertGPS(boat.getWake());
|
||||
displayLine(wakeFrom, wakeTo, boat.getColour());
|
||||
}
|
||||
else {
|
||||
displayBoat(boat, 0);
|
||||
}
|
||||
|
||||
if (raceAnno) displayText(boat.getAbbrev(), boat.getVelocity(), this.map.convertGPS(boat.getCurrentPosition()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the Canvas to resizable.
|
||||
*
|
||||
@@ -252,4 +291,17 @@ public class ResizableRaceCanvas extends Canvas {
|
||||
return getHeight();
|
||||
}
|
||||
|
||||
/**
|
||||
* Draws boats during race setup, when leg heading is not set.
|
||||
*/
|
||||
public void drawBoats() {
|
||||
if (boats != null) {
|
||||
for (BoatInRace boat : boats) {
|
||||
if (boat != null) {
|
||||
displayBoat(boat, 0);
|
||||
if (raceAnno) displayText(boat.getAbbrev(), boat.getVelocity(), this.map.convertGPS(boat.getCurrentPosition()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,8 +6,8 @@
|
||||
<?import javafx.scene.control.*?>
|
||||
<?import javafx.scene.layout.*?>
|
||||
<?import javafx.scene.text.*?>
|
||||
|
||||
<AnchorPane xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="seng302.Controllers.RaceController">
|
||||
<AnchorPane xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1"
|
||||
fx:controller="seng302.Controllers.RaceController">
|
||||
<children>
|
||||
<GridPane fx:id="startScreen" prefHeight="600.0" prefWidth="780.0">
|
||||
<columnConstraints>
|
||||
@@ -27,7 +27,7 @@
|
||||
<children>
|
||||
<Text strokeType="OUTSIDE" strokeWidth="0.0" text="Select Your Race Scaling:" GridPane.columnIndex="1" GridPane.rowIndex="1">
|
||||
<font>
|
||||
<Font size="23.0" />
|
||||
<Font size="23.0"/>
|
||||
</font>
|
||||
</Text>
|
||||
<Button mnemonicParsing="false" onAction="#startRace1Min" text="15x faster" GridPane.columnIndex="1" GridPane.rowIndex="2" />
|
||||
@@ -35,26 +35,26 @@
|
||||
<Button mnemonicParsing="false" onAction="#startRace5Min" text="3x faster" GridPane.columnIndex="2" GridPane.rowIndex="2" />
|
||||
<Label alignment="CENTER" text="Race will take ~1 minute" GridPane.columnIndex="1" GridPane.rowIndex="3">
|
||||
<opaqueInsets>
|
||||
<Insets />
|
||||
<Insets/>
|
||||
</opaqueInsets>
|
||||
<font>
|
||||
<Font size="10.0" />
|
||||
<Font size="10.0"/>
|
||||
</font>
|
||||
</Label>
|
||||
<Label alignment="CENTER" layoutX="99.0" layoutY="407.0" text="Race will take ~5 minutes" GridPane.columnIndex="2" GridPane.rowIndex="3">
|
||||
<font>
|
||||
<Font size="10.0" />
|
||||
<Font size="10.0"/>
|
||||
</font>
|
||||
<opaqueInsets>
|
||||
<Insets />
|
||||
<Insets/>
|
||||
</opaqueInsets>
|
||||
</Label>
|
||||
<Label alignment="CENTER" layoutX="279.0" layoutY="407.0" text="Race will take ~15 minutes" GridPane.columnIndex="3" GridPane.rowIndex="3">
|
||||
<font>
|
||||
<Font size="10.0" />
|
||||
<Font size="10.0"/>
|
||||
</font>
|
||||
<opaqueInsets>
|
||||
<Insets />
|
||||
<Insets/>
|
||||
</opaqueInsets>
|
||||
</Label>
|
||||
</children>
|
||||
@@ -87,10 +87,10 @@
|
||||
<children>
|
||||
<TableView fx:id="boatInfoTable" layoutX="-2.0" prefHeight="600.0" prefWidth="264.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="-2.0" AnchorPane.rightAnchor="-62.0" AnchorPane.topAnchor="0.0">
|
||||
<columns>
|
||||
<TableColumn fx:id="boatPlacingColumn" prefWidth="50.0" text="Place" />
|
||||
<TableColumn fx:id="boatTeamColumn" prefWidth="100.0" text="Team" />
|
||||
<TableColumn fx:id="boatMarkColumn" prefWidth="130.0" text="Mark" />
|
||||
<TableColumn fx:id="boatSpeedColumn" prefWidth="75.0" text="Speed" />
|
||||
<TableColumn fx:id="boatPlacingColumn" prefWidth="50.0" text="Place"/>
|
||||
<TableColumn fx:id="boatTeamColumn" prefWidth="100.0" text="Team"/>
|
||||
<TableColumn fx:id="boatMarkColumn" prefWidth="130.0" text="Mark"/>
|
||||
<TableColumn fx:id="boatSpeedColumn" prefWidth="75.0" text="Speed"/>
|
||||
</columns>
|
||||
</TableView>
|
||||
</children>
|
||||
@@ -98,4 +98,5 @@
|
||||
</items>
|
||||
</SplitPane>
|
||||
</children>
|
||||
>>>>>>> Temporary merge branch 2
|
||||
</AnchorPane>
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package seng302.Model;
|
||||
|
||||
import javafx.scene.paint.Color;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
import seng302.GPSCoordinate;
|
||||
|
||||
@@ -109,4 +111,43 @@ public class BoatInRaceTest {
|
||||
assertEquals(testBoat.getVelocity(), 20.0);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void getWakeAtProperHeading() throws Exception {
|
||||
BoatInRace boat = new BoatInRace("Test", 1, Color.ALICEBLUE, "tt");
|
||||
|
||||
// Construct leg of 0 degrees
|
||||
GPSCoordinate startPoint = new GPSCoordinate(0, 0);
|
||||
GPSCoordinate endPoint = new GPSCoordinate(50, 0);
|
||||
Leg leg0deg = new Leg("Start", startPoint, endPoint, 0);
|
||||
boat.setCurrentLeg(leg0deg);
|
||||
boat.setCurrentPosition(new GPSCoordinate(0,0));
|
||||
|
||||
assertEquals(0, boat.calculateHeading(), 1e-8);
|
||||
|
||||
// Construct leg from wake - heading should be 180 degrees
|
||||
Leg leg180deg = new Leg("Start", startPoint, boat.getWake(), 0);
|
||||
boat.setCurrentLeg(leg180deg);
|
||||
|
||||
assertEquals(180, boat.calculateHeading(), 1e-8);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getWakeProportionalToVelocity() throws Exception {
|
||||
BoatInRace boat = new BoatInRace("Test", 10, Color.ALICEBLUE, "tt");
|
||||
|
||||
// Construct leg of 0 degrees at 0 N
|
||||
GPSCoordinate startPoint = new GPSCoordinate(0, 0);
|
||||
GPSCoordinate endPoint = new GPSCoordinate(50, 0);
|
||||
Leg leg0deg = new Leg("Start", startPoint, endPoint, 0);
|
||||
boat.setCurrentLeg(leg0deg);
|
||||
boat.setCurrentPosition(new GPSCoordinate(0,0));
|
||||
|
||||
// Get latitude of endpoint of wake at 10 kn (longitude is 0)
|
||||
double endpointAt10Kn = boat.getWake().getLatitude();
|
||||
|
||||
// Latitude of endpoint at 20 kn should be twice endpoint at 10 kn
|
||||
boat.setVelocity(20);
|
||||
assertEquals(2*endpointAt10Kn, boat.getWake().getLatitude(), 1e-8);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user