Merge remote-tracking branch 'origin/develop' into issue#5_fix_pre-race_boats_on_leaderboard

This commit is contained in:
Zhi You Tan
2017-05-18 12:20:02 +12:00
11 changed files with 311 additions and 251 deletions
+2 -1
View File
@@ -62,7 +62,8 @@ public class App extends Application {
} }
//Change the StreamReceiver in this else block to change the default data source. //Change the StreamReceiver in this else block to change the default data source.
else{ else{
sr = new StreamReceiver("localhost", 4949, "RaceStream"); //sr = new StreamReceiver("localhost", 4949, "RaceStream");
sr = new StreamReceiver("livedata.americascup.com", 4941, "RaceStream");
} }
sr.start(); sr.start();
@@ -25,7 +25,7 @@ public class RaceController {
String teamsConfigFile = "/config/teams.xml"; String teamsConfigFile = "/config/teams.xml";
try { try {
race = createRace(raceConfigFile, teamsConfigFile); race = createRace(raceConfigFile, teamsConfigFile); //These config files arent actually used
} catch (Exception e) { } catch (Exception e) {
System.out.println("There was an error creating the race."); System.out.println("There was an error creating the race.");
} }
@@ -2,13 +2,14 @@ package seng302.controllers;
import javafx.animation.KeyFrame; import javafx.animation.KeyFrame;
import javafx.animation.Timeline; import javafx.animation.Timeline;
import javafx.beans.value.ChangeListener; import javafx.collections.FXCollections;
import javafx.beans.value.ObservableValue; import javafx.collections.ObservableList;
import javafx.fxml.FXML; import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader; import javafx.fxml.FXMLLoader;
import javafx.scene.Scene; import javafx.scene.Scene;
import javafx.scene.control.Button; import javafx.scene.control.Button;
import javafx.scene.control.CheckBox; import javafx.scene.control.CheckBox;
import javafx.scene.control.ComboBox;
import javafx.scene.control.Slider; import javafx.scene.control.Slider;
import javafx.scene.layout.AnchorPane; import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.Pane; import javafx.scene.layout.Pane;
@@ -50,6 +51,8 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel
@FXML @FXML
private Button selectAnnotationBtn; private Button selectAnnotationBtn;
@FXML @FXML
private ComboBox boatSelectionComboBox;
@FXML
private CanvasController includedCanvasController; private CanvasController includedCanvasController;
private ArrayList<Yacht> startingBoats = new ArrayList<>(); private ArrayList<Yacht> startingBoats = new ArrayList<>();
@@ -57,26 +60,27 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel
private Timeline timerTimeline; private Timeline timerTimeline;
private Race race; private Race race;
private Stage stage; private Stage stage;
private ImportantAnnotationsState importantAnnotations; private ImportantAnnotationsState importantAnnotations;
private Yacht selectedBoat;
public void initialize() { public void initialize() {
// Load a default important annotation state // Load a default important annotation state
importantAnnotations = new ImportantAnnotationsState(); importantAnnotations = new ImportantAnnotationsState();
//Initialise race controller
RaceController raceController = new RaceController(); RaceController raceController = new RaceController();
raceController.initializeRace(); raceController.initializeRace();
race = raceController.getRace(); race = raceController.getRace();
for (Yacht boat : race.getBoats()) { startingBoats = new ArrayList<>(Arrays.asList(race.getBoats()));
startingBoats.add(boat);
}
includedCanvasController.setup(this); includedCanvasController.setup(this);
includedCanvasController.initializeCanvas(); includedCanvasController.initializeCanvas();
initializeTimer(); initializeUpdateTimer();
initializeSettings(); initialiseFPSCheckBox();
initialiseWindDirection(); initialiseAnnotationSlider();
initialisePositionVBox(); initialiseBoatSelectionComboBox();
includedCanvasController.timer.start(); includedCanvasController.timer.start();
selectAnnotationBtn.setOnAction(event -> { selectAnnotationBtn.setOnAction(event -> {
@@ -86,7 +90,6 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel
/** /**
* The important annotations have been changed, update this view * The important annotations have been changed, update this view
*
* @param importantAnnotationsState The current state of the selected annotations * @param importantAnnotationsState The current state of the selected annotations
*/ */
public void importantAnnotationsChanged(ImportantAnnotationsState importantAnnotationsState) { public void importantAnnotationsChanged(ImportantAnnotationsState importantAnnotationsState) {
@@ -110,7 +113,7 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel
// Load FXML and set CSS // Load FXML and set CSS
fxmlLoader fxmlLoader
.setLocation(getClass().getResource("/views/importantAnnotationSelectView.fxml")); .setLocation(getClass().getResource("/views/importantAnnotationSelectView.fxml"));
Scene scene = new Scene(fxmlLoader.load(), 469, 270); Scene scene = new Scene(fxmlLoader.load(), 469, 248);
scene.getStylesheets().add(getClass().getResource("/css/master.css").toString()); scene.getStylesheets().add(getClass().getResource("/css/master.css").toString());
stage.initStyle(StageStyle.UNDECORATED); stage.initStyle(StageStyle.UNDECORATED);
@@ -124,18 +127,14 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel
} }
} }
private void initializeSettings() {
private void initialiseFPSCheckBox() {
displayFps = true; displayFps = true;
toggleFps.selectedProperty().addListener(
toggleFps.selectedProperty().addListener(new ChangeListener<Boolean>() { (observable, oldValue, newValue) -> displayFps = !displayFps);
@Override
public void changed(ObservableValue<? extends Boolean> observable, Boolean oldValue,
Boolean newValue) {
displayFps = !displayFps;
} }
});
//SLIDER STUFF BELOW private void initialiseAnnotationSlider() {
annotationSlider.setLabelFormatter(new StringConverter<Double>() { annotationSlider.setLabelFormatter(new StringConverter<Double>() {
@Override @Override
public String toString(Double n) { public String toString(Double n) {
@@ -143,12 +142,9 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel
return "None"; return "None";
} }
if (n == 1) { if (n == 1) {
return "Low";
}
if (n == 2) {
return "Important"; return "Important";
} }
if (n == 3) { if (n == 2) {
return "All"; return "All";
} }
@@ -160,15 +156,13 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel
switch (s) { switch (s) {
case "None": case "None":
return 0d; return 0d;
case "Low":
return 1d;
case "Important": case "Important":
return 2d; return 1d;
case "All": case "All":
return 3d; return 2d;
default: default:
return 3d; return 2d;
} }
} }
}); });
@@ -176,22 +170,27 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel
annotationSlider.valueProperty().addListener((obs, oldval, newVal) -> annotationSlider.valueProperty().addListener((obs, oldval, newVal) ->
setAnnotations((int) annotationSlider.getValue())); setAnnotations((int) annotationSlider.getValue()));
annotationSlider.setValue(3); annotationSlider.setValue(2);
} }
private void initializeTimer() {
/**
* Initalises a timer which updates elements of the RaceView such as wind direction, boat
* orderings etc.. which are dependent on the info from the stream parser constantly.
* Updates of each of these attributes are called ONCE EACH SECOND
*/
private void initializeUpdateTimer() {
timerTimeline = new Timeline(); timerTimeline = new Timeline();
timerTimeline.setCycleCount(Timeline.INDEFINITE); timerTimeline.setCycleCount(Timeline.INDEFINITE);
// Run timer update every second // Run timer update every second
timerTimeline.getKeyFrames().add( timerTimeline.getKeyFrames().add(
new KeyFrame(Duration.seconds(1), new KeyFrame(Duration.seconds(1),
event -> { event -> {
if (StreamParser.isRaceFinished()) { updateRaceTime();
timerLabel.setFill(Color.RED); updateWindDirection();
timerLabel.setText("Race Finished!"); updateOrder();
} else { updateBoatSelectionComboBox();
timerLabel.setText(currentTimer());
}
}) })
); );
@@ -199,54 +198,45 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel
timerTimeline.playFromStart(); timerTimeline.playFromStart();
} }
private void initialiseWindDirection() {
Timeline windDirTimeline = new Timeline();
windDirTimeline.setCycleCount(Timeline.INDEFINITE);
windDirTimeline.getKeyFrames().add(
new KeyFrame(Duration.seconds(1),
event -> {
windDirectionText
.setText(String.format("%.1f°", StreamParser.getWindDirection()));
windArrowText.setRotate(StreamParser.getWindDirection());
})
);
windDirTimeline.playFromStart();
}
private void initialisePositionVBox() {
Timeline posVBoxTimeline = new Timeline();
posVBoxTimeline.setCycleCount(Timeline.INDEFINITE);
posVBoxTimeline.getKeyFrames().add(
new KeyFrame(Duration.seconds(1),
event -> {
showOrder();
})
);
posVBoxTimeline.playFromStart();
}
/** /**
* Display the list of boats in the order they finished the race * Updates the wind direction arrow and text as from info from the StreamParser
*/ */
private void loadRaceResultView() { private void updateWindDirection() {
FXMLLoader loader = new FXMLLoader(getClass().getResource("/views/FinishView.fxml")); windDirectionText.setText(String.format("%.1f°", StreamParser.getWindDirection()));
loader.setController(new RaceResultController(race)); windArrowText.setRotate(StreamParser.getWindDirection());
}
try {
contentAnchorPane.getChildren().removeAll();
contentAnchorPane.getChildren().clear();
contentAnchorPane.getChildren().addAll((Pane) loader.load());
} catch (javafx.fxml.LoadException e) { /**
System.err.println(e.getCause()); * Updates the clock for the race
} catch (IOException e) { */
System.err.println(e); private void updateRaceTime() {
if (StreamParser.isRaceFinished()) {
timerLabel.setFill(Color.RED);
timerLabel.setText("Race Finished!");
} else {
timerLabel.setText(getTimeSinceStartOfRace());
} }
} }
private void showOrder() {
/**
* Grabs the boats currently in the race as from the StreamParser and sets them to be selectable
* in the boat selection combo box
*/
private void updateBoatSelectionComboBox() {
ObservableList<Yacht> observableBoats = FXCollections
.observableArrayList(StreamParser.getBoatsPos().values());
boatSelectionComboBox.setItems(observableBoats);
}
/**
* Updates the order of the boats as from the StreamParser and sets them in the boat order
* section
*/
private void updateOrder() {
positionVbox.getChildren().clear(); positionVbox.getChildren().clear();
positionVbox.getChildren().removeAll(); positionVbox.getChildren().removeAll();
positionVbox.getStylesheets().add(getClass().getResource("/css/master.css").toString()); positionVbox.getStylesheets().add(getClass().getResource("/css/master.css").toString());
@@ -279,6 +269,44 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel
} }
} }
/**
* Initialised the combo box with any boats currently in the race and adds the required listener
* for the combobox to take action upon selection
*/
private void initialiseBoatSelectionComboBox() {
updateBoatSelectionComboBox();
boatSelectionComboBox.valueProperty().addListener((observable, oldValue, newValue) -> {
//This listener is fired whenever the combo box changes. This means when the values are updated
//We dont want to set the selected value if the values are updated but nothing clicked (null)
if (newValue != null && newValue != selectedBoat) {
Yacht thisYacht = (Yacht) newValue;
setSelectedBoat(thisYacht);
}
});
}
/**
* Display the list of boats in the order they finished the race
*/
private void loadRaceResultView() {
FXMLLoader loader = new FXMLLoader(getClass().getResource("/views/FinishView.fxml"));
loader.setController(new RaceResultController(race));
try {
contentAnchorPane.getChildren().removeAll();
contentAnchorPane.getChildren().clear();
contentAnchorPane.getChildren().addAll((Pane) loader.load());
} catch (javafx.fxml.LoadException e) {
System.err.println(e.getCause());
} catch (IOException e) {
System.err.println(e);
}
}
/** /**
* Convert seconds to a string of the format mm:ss * Convert seconds to a string of the format mm:ss
* *
@@ -292,7 +320,7 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel
return String.format("%02d:%02d", time / 60, time % 60); return String.format("%02d:%02d", time / 60, time % 60);
} }
private String currentTimer() { private String getTimeSinceStartOfRace() {
String timerString = "0:00"; String timerString = "0:00";
if (StreamParser.getTimeSinceStart() > 0) { if (StreamParser.getTimeSinceStart() > 0) {
String timerMinute = Long.toString(StreamParser.getTimeSinceStart() / 60); String timerMinute = Long.toString(StreamParser.getTimeSinceStart() / 60);
@@ -312,13 +340,6 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel
return timerString; return timerString;
} }
public void stopTimer() {
timerTimeline.stop();
}
public void startTimer() {
timerTimeline.play();
}
public boolean isDisplayFps() { public boolean isDisplayFps() {
return displayFps; return displayFps;
@@ -328,13 +349,9 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel
return race; return race;
} }
public ArrayList<Yacht> getStartingBoats() {
return startingBoats;
}
/** /**
* Display the important annotations for a specific BoatGroup * Display the important annotations for a specific BoatGroup
*
* @param bg The boat group to set the annotations for * @param bg The boat group to set the annotations for
*/ */
private void setBoatGroupImportantAnnotations(BoatGroup bg) { private void setBoatGroupImportantAnnotations(BoatGroup bg) {
@@ -391,22 +408,8 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel
} }
} }
break; break;
// Low Annotations
case 1:
for (RaceObject ro : includedCanvasController.getRaceObjects()) {
if (ro instanceof BoatGroup) {
BoatGroup bg = (BoatGroup) ro;
bg.setTeamNameObjectVisible(true);
bg.setVelocityObjectVisible(false);
bg.setEstTimeToNextMarkObjectVisible(false);
bg.setLegTimeObjectVisible(false);
bg.setLineGroupVisible(false);
bg.setWakeVisible(false);
}
}
break;
// Important Annotations // Important Annotations
case 2: case 1:
for (RaceObject ro : includedCanvasController.getRaceObjects()) { for (RaceObject ro : includedCanvasController.getRaceObjects()) {
if (ro instanceof BoatGroup) { if (ro instanceof BoatGroup) {
BoatGroup bg = (BoatGroup) ro; BoatGroup bg = (BoatGroup) ro;
@@ -415,7 +418,7 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel
} }
break; break;
// All Annotations // All Annotations
case 3: case 2:
for (RaceObject ro : includedCanvasController.getRaceObjects()) { for (RaceObject ro : includedCanvasController.getRaceObjects()) {
if (ro instanceof BoatGroup) { if (ro instanceof BoatGroup) {
BoatGroup bg = (BoatGroup) ro; BoatGroup bg = (BoatGroup) ro;
@@ -431,6 +434,28 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel
} }
} }
/**
* Sets all the annotations of the selected boat to be visible and all others to be hidden
*
* @param yacht The yacht for which we want to view all annotations
*/
private void setSelectedBoat(Yacht yacht) {
for (RaceObject ro : includedCanvasController.getRaceObjects()) {
if (ro instanceof BoatGroup) {
BoatGroup bg = (BoatGroup) ro;
//We need to iterate over all race groups to get the matching boat group belonging to this boat if we
//are to toggle its annotations, there is no other backwards knowledge of a yacht to its boatgroup.
if (bg.getBoat().getHullID().equals(yacht.getHullID())) {
bg.setIsSelected(true);
selectedBoat = yacht;
} else {
bg.setIsSelected(false);
}
}
}
}
void setStage(Stage stage) { void setStage(Stage stage) {
this.stage = stage; this.stage = stage;
} }
+45 -41
View File
@@ -1,6 +1,7 @@
package seng302.models; package seng302.models;
import javafx.geometry.Point2D; import javafx.geometry.Point2D;
import javafx.scene.CacheHint;
import javafx.scene.Group; import javafx.scene.Group;
import javafx.scene.paint.Color; import javafx.scene.paint.Color;
import javafx.scene.shape.Line; import javafx.scene.shape.Line;
@@ -49,6 +50,7 @@ public class BoatGroup extends RaceObject {
private Text estTimeToNextMarkObject; private Text estTimeToNextMarkObject;
private Text legTimeObject; private Text legTimeObject;
private Wake wake; private Wake wake;
private boolean isSelected = true; //Boats annotations are visible by default at the start
//Handles boat moving when connecting to a stream //Handles boat moving when connecting to a stream
private boolean setToInitialLocation = false; private boolean setToInitialLocation = false;
private boolean destinationSet; private boolean destinationSet;
@@ -95,8 +97,16 @@ public class BoatGroup extends RaceObject {
private void initChildren(Color color, double... points) { private void initChildren(Color color, double... points) {
boatPoly = new Polygon(points); boatPoly = new Polygon(points);
boatPoly.setFill(color); boatPoly.setFill(color);
boatPoly.setOnMouseEntered(event -> boatPoly.setFill(Color.FLORALWHITE));
boatPoly.setOnMouseExited(event -> boatPoly.setFill(color));
boatPoly.setOnMouseClicked(event -> setIsSelected(!isSelected));
boatPoly.setCache(true);
boatPoly.setCacheHint(CacheHint.SPEED);
teamNameObject = new Text(boat.getShortName()); teamNameObject = new Text(boat.getShortName());
teamNameObject.setCache(true);
teamNameObject.setCacheHint(CacheHint.SPEED);
velocityObject = new Text(String.valueOf(boat.getVelocity())); velocityObject = new Text(String.valueOf(boat.getVelocity()));
DateFormat format = new SimpleDateFormat("mm:ss"); DateFormat format = new SimpleDateFormat("mm:ss");
String timeToNextMark = format String timeToNextMark = format
@@ -109,6 +119,8 @@ public class BoatGroup extends RaceObject {
} else { } else {
legTimeObject = new Text("Last mark: -"); legTimeObject = new Text("Last mark: -");
} }
velocityObject.setCache(true);
velocityObject.setCacheHint(CacheHint.SPEED);
teamNameObject.setX(TEAMNAME_X_OFFSET); teamNameObject.setX(TEAMNAME_X_OFFSET);
teamNameObject.setY(TEAMNAME_Y_OFFSET); teamNameObject.setY(TEAMNAME_Y_OFFSET);
@@ -210,8 +222,6 @@ public class BoatGroup extends RaceObject {
* on. * on.
*/ */
public void updatePosition(long timeInterval) { public void updatePosition(long timeInterval) {
//Calculate the movement of the boat.
if (isMaximized) {
double dx = pixelVelocityX * timeInterval; double dx = pixelVelocityX * timeInterval;
double dy = pixelVelocityY * timeInterval; double dy = pixelVelocityY * timeInterval;
double rotation = rotationalVelocity * timeInterval; double rotation = rotationalVelocity * timeInterval;
@@ -228,7 +238,7 @@ public class BoatGroup extends RaceObject {
boatPoly.getLayoutY() boatPoly.getLayoutY()
); );
l.getStrokeDashArray().setAll(3d, 7d); l.getStrokeDashArray().setAll(3d, 7d);
l.setStroke(boatPoly.getFill()); l.setStroke(boat.getColour());
lineGroup.getChildren().add(l); lineGroup.getChildren().add(l);
} }
if (destinationSet) { //Only begin drawing after the first destination is set if (destinationSet) { //Only begin drawing after the first destination is set
@@ -237,7 +247,6 @@ public class BoatGroup extends RaceObject {
} }
wake.updatePosition(timeInterval); wake.updatePosition(timeInterval);
} }
}
/** /**
* Sets the destination of the boat and the headng it should have once it reaches * Sets the destination of the boat and the headng it should have once it reaches
@@ -253,32 +262,21 @@ public class BoatGroup extends RaceObject {
if (setToInitialLocation) { if (setToInitialLocation) {
destinationSet = true; destinationSet = true;
boat.setVelocity(groundSpeed); boat.setVelocity(groundSpeed);
if (currentRotation < 0) {
currentRotation = 360 - currentRotation;
}
double dx = newXValue - boatPoly.getLayoutX(); double dx = newXValue - boatPoly.getLayoutX();
double dy = newYValue - boatPoly.getLayoutY(); double dy = newYValue - boatPoly.getLayoutY();
//Check movement is reasonable. Assumes a 1000 * 1000 canvas
if (Math.abs(dx) > 50 || Math.abs(dy) > 50) {
dx = 0;
dy = 0;
moveTo(newXValue, newYValue);
}
pixelVelocityX = dx / expectedUpdateInterval; pixelVelocityX = dx / expectedUpdateInterval;
pixelVelocityY = dy / expectedUpdateInterval; pixelVelocityY = dy / expectedUpdateInterval;
rotationalGoal = rotation; rotationalGoal = rotation;
calculateRotationalVelocity(); calculateRotationalVelocity();
if (wakeGenerationDelay > 0) { if (Math.abs(rotationalVelocity) > 0.075) {
wake.rotate(rotationalGoal);
rotateTo(rotationalGoal); //Need to test with this removed.
rotationalVelocity = 0; rotationalVelocity = 0;
wakeGenerationDelay--; rotateTo(rotationalGoal);
} else { wake.rotate(rotationalGoal);
wake.setRotationalVelocity(rotationalVelocity, rotationalGoal,
boat.getVelocity());
} }
wake.setRotationalVelocity(rotationalVelocity, boat.getVelocity());
velocityObject.setText(String.format("%.2f m/s", boat.getVelocity())); velocityObject.setText(String.format("%.2f m/s", boat.getVelocity()));
DateFormat format = new SimpleDateFormat("mm:ss"); DateFormat format = new SimpleDateFormat("mm:ss");
// estimate time to next mark // estimate time to next mark
@@ -300,27 +298,6 @@ public class BoatGroup extends RaceObject {
} }
} }
//If minimized generate lines every 5 calls to set destination. //If minimized generate lines every 5 calls to set destination.
if (!isMaximized) {
setToInitialLocation = false;
wakeGenerationDelay = 2;
if (setCallCount-- == 0) {
setCallCount = 5;
if (lastPoint != null) {
Line l = new Line(
lastPoint.getX(),
lastPoint.getY(),
newXValue,
newYValue
);
l.getStrokeDashArray().setAll(3d, 7d);
l.setStroke(boatPoly.getFill());
lineStorage.add(l);
}
if (destinationSet) { //Only begin drawing after the first destination is set
lastPoint = new Point2D(newXValue, newYValue);
}
}
}
} }
public void setDestination(double newXValue, double newYValue, double groundSpeed, public void setDestination(double newXValue, double newYValue, double groundSpeed,
@@ -349,6 +326,10 @@ public class BoatGroup extends RaceObject {
wake.rotate(rotationalGoal); wake.rotate(rotationalGoal);
} }
public void paintBoat(Color color) {
boatPoly.setFill(color);
}
public void setTeamNameObjectVisible(Boolean visible) { public void setTeamNameObjectVisible(Boolean visible) {
teamNameObject.setVisible(visible); teamNameObject.setVisible(visible);
} }
@@ -377,6 +358,24 @@ public class BoatGroup extends RaceObject {
return boat; return boat;
} }
/**
* This function sets the boats isSelected property AS WELL as actually acting upon the value of
* that selection. (Painting or not painting annotations)
*
* @param isSelected A Boolean indicating whether or not the boat is selected
*/
public void setIsSelected(Boolean isSelected) {
this.isSelected = isSelected;
setTeamNameObjectVisible(isSelected);
setVelocityObjectVisible(isSelected);
setLineGroupVisible(isSelected);
setWakeVisible(isSelected);
setEstTimeToNextMarkObjectVisible(isSelected);
setLegTimeObjectVisible(isSelected);
// TODO: 17/05/17 wmu16 - this should iterate over some list of annotations which we should make to easily make extensible
// paintBoat((isSelected) ? Color.WHITE : boat.getColour());
}
/** /**
* Returns true if this BoatGroup contains at least one of the given IDs. * Returns true if this BoatGroup contains at least one of the given IDs.
* *
@@ -435,4 +434,9 @@ public class BoatGroup extends RaceObject {
} }
}); });
} }
@Override
public String toString() {
return boat.toString();
}
} }
+50 -43
View File
@@ -1,30 +1,34 @@
package seng302.models; package seng302.models;
import javafx.scene.CacheHint;
import javafx.scene.Group; import javafx.scene.Group;
import javafx.scene.paint.Color; import javafx.scene.paint.Color;
import javafx.scene.shape.Arc; import javafx.scene.shape.Arc;
import javafx.scene.shape.ArcType; import javafx.scene.shape.ArcType;
import javafx.scene.shape.StrokeLineCap;
import javafx.scene.transform.Rotate; import javafx.scene.transform.Rotate;
/** /**
* By default wake is a group containing 5 arcs. Each arc starts from the same point. Each arc is larger and more * A group containing objects used to represent wakes onscreen. Contains functionality for their animation.
* transparent than the last. On calling updatePositions() arcs rotate at velocities given by setRotationalVelocity().
* The larger and more transparent an arc is the longer the delay before it rotates at the latest velocity. It is
* assumed that rotationalVelocities() are set regularly as wakes do not stop rotating and an array of velocities needs
* to be populated for the class to work as expected.
*/ */
class Wake extends Group { class Wake extends Group {
private int numWakes = 5; //The number of wakes
private double[] velocities = new double[13]; private int numWakes = 8;
//The total possible difference between the first wake and the last. Increasing/Decreasing this will make wakes fan out more/less.
private final double MAX_DIFF = 75;
//Increasing/decreasing this will alter the speed that wakes converge when the heading stop changing. Anything over about 1500 may cause oscillation.
private final int UNIFICATION_SPEED = 750;
private Arc[] arcs = new Arc[numWakes]; private Arc[] arcs = new Arc[numWakes];
private double[] rotationalVelocities = new double[numWakes];
private double[] rotations = new double[numWakes]; private double[] rotations = new double[numWakes];
private int[] velocityIndices = new int[numWakes]; private double baseRad;
private double sum = 0;
private static double max;
/** /**
* Create a wake at the given location. * Create a wake at the given location.
*
* @param startingX x location where the tip of wake arcs will be. * @param startingX x location where the tip of wake arcs will be.
* @param startingY y location where the tip of wake arcs will be. * @param startingY y location where the tip of wake arcs will be.
*/ */
@@ -35,73 +39,76 @@ class Wake extends Group {
for (int i = 0; i < numWakes; i++) { for (int i = 0; i < numWakes; i++) {
//Default triangle is -110 deg out of phase with a default wake and has angle of 40 deg. //Default triangle is -110 deg out of phase with a default wake and has angle of 40 deg.
arc = new Arc(0, 0, 0, 0, -110, 40); arc = new Arc(0, 0, 0, 0, -110, 40);
//Opacity increases from 0.5 -> 0 evenly over the 5 wake arcs. arc.setCache(true);
arc.setFill(new Color(0.18, 0.7, 1.0, 1.0 + -0.175 * i)); arc.setCacheHint(CacheHint.SPEED);
arc.setType(ArcType.ROUND); arc.setType(ArcType.OPEN);
arc.setStroke(new Color(0.18, 0.7, 1.0, 1.0 + (-0.99 / numWakes * i)));
arc.setStrokeWidth(3.0);
arc.setStrokeLineCap(StrokeLineCap.ROUND);
arc.setFill(new Color(0.0, 0.0, 0.0, 0.0));
baseRad = (20 / numWakes);
arcs[i] = arc; arcs[i] = arc;
} }
super.getChildren().addAll(arcs); super.getChildren().addAll(arcs);
} }
/** /**
* Sets the rotationalVelocity of each arc. Each arc is 3 velocities behind the next smallest arc. The smallest uses * Sets the rotationalVelocity of each arc.
* the latest given velocity. *
* @param rotationalVelocity The rotationalVelocity the wake should move at. * @param rotationalVelocity The rotationalVelocity the wake should move at.
* @param rotationGoal Where the wake will rotate to if the wake is calculated to be on a straight section. This is
* used to prevent desynchronisation with the Boat polygon.
* @param velocity The real world velocity of the boat in m/s. * @param velocity The real world velocity of the boat in m/s.
*/ */
void setRotationalVelocity (double rotationalVelocity, double rotationGoal, double velocity) { void setRotationalVelocity(double rotationalVelocity, double velocity) {
sum -= Math.abs(velocities[(velocityIndices[0] + 10) % 13]); rotationalVelocities[0] = rotationalVelocity;
sum += Math.abs(rotationalVelocity); for (int i = 1; i < numWakes; i++) {
max = Math.max(max, rotationalVelocity); double wakeSeparationRad = Math.toRadians(rotations[i - 1] - rotations[i]);
if (sum < (max / 3)) double shortestDistance = Math.atan2(
rotate (rotationGoal); //In relatively straight segments the wake snaps to match the boats current position. Math.sin(wakeSeparationRad),
//This stops the wake from eventually becoming out of sync with the boat. Math.cos(wakeSeparationRad)
//This accounts for rogue rotations that are greater than what would be realistic. Value is kinda rough. );
//Basically just for our internal mock. double distDeg = Math.toDegrees(shortestDistance);
if (Math.abs(rotationalVelocity) > 0.05) {
rotationalVelocity = 0;
rotate(rotationGoal);
}
//Update the index of the array of recent velocities that each wake uses. Each wake is 3 velocities behind the
//next smallest wake.
velocityIndices[0] = (13 + (velocityIndices[0] - 1) % 13) % 13;
velocities[velocityIndices[0]] = rotationalVelocity;
for (int i = 1; i < numWakes; i++)
velocityIndices[i] = (velocityIndices[0] + 3 * i) % 13;
//Scale wakes based on velocity. if (rotationalVelocities[i - 1] < 0.01 && rotationalVelocities[i - 1] > -0.01) {
double baseRad = 20; rotationalVelocities[i] = distDeg / UNIFICATION_SPEED * Math.log(Math.abs(distDeg) + 1) / Math.log(MAX_DIFF / numWakes);
double rad;
} else {
if (distDeg < (MAX_DIFF / numWakes))
rotationalVelocities[i] = rotationalVelocities[i - 1] * Math.log(Math.abs(distDeg) + 1) / Math.log(MAX_DIFF / numWakes);
else
rotationalVelocities[i] = rotationalVelocities[i - 1];
}
}
double rad = baseRad + velocity;
for (Arc arc : arcs) { for (Arc arc : arcs) {
rad = baseRad + velocity;
arc.setRadiusX(rad); arc.setRadiusX(rad);
arc.setRadiusY(rad); arc.setRadiusY(rad);
baseRad += 5 + (velocity / 2); rad += (20 / numWakes) + (velocity / 2);
} }
} }
/** /**
* Arcs rotate based on the distance they would have travelled over the supplied time interval. * Arcs rotate based on the distance they would have travelled over the supplied time interval.
*
* @param timeInterval the time interval, in microseconds, that the wake should move. * @param timeInterval the time interval, in microseconds, that the wake should move.
*/ */
void updatePosition(long timeInterval) { void updatePosition(long timeInterval) {
for (int i = 0; i < numWakes; i++) { for (int i = 0; i < numWakes; i++) {
rotations[i] = rotations[i] + velocities[velocityIndices[i]] * timeInterval; rotations[i] = rotations[i] + rotationalVelocities[i] * timeInterval;
arcs[i].getTransforms().setAll(new Rotate(rotations[i])); arcs[i].getTransforms().setAll(new Rotate(rotations[i]));
} }
} }
/** /**
* Rotate all wakes to the given rotation. * Rotate all wakes to the given rotation.
*
* @param rotation the from north angle in degrees to rotate to. * @param rotation the from north angle in degrees to rotate to.
*/ */
void rotate(double rotation) { void rotate(double rotation) {
for (int i = 0; i < arcs.length; i++) { for (int i = 0; i < arcs.length; i++) {
rotations[i] = rotation; rotations[i] = rotation;
rotationalVelocities[i] = 0;
arcs[i].getTransforms().setAll(new Rotate(rotation)); arcs[i].getTransforms().setAll(new Rotate(rotation));
} }
} }
} }
+5
View File
@@ -174,4 +174,9 @@ public class Yacht {
public void setMarkRoundingTime(Long markRoundingTime) { public void setMarkRoundingTime(Long markRoundingTime) {
this.markRoundingTime = markRoundingTime; this.markRoundingTime = markRoundingTime;
} }
@Override
public String toString() {
return boatName;
}
} }
@@ -1,6 +1,7 @@
package seng302.models.mark; package seng302.models.mark;
import javafx.geometry.Point2D; import javafx.geometry.Point2D;
import javafx.scene.CacheHint;
import javafx.scene.Node; import javafx.scene.Node;
import javafx.scene.paint.Color; import javafx.scene.paint.Color;
import javafx.scene.shape.Circle; import javafx.scene.shape.Circle;
@@ -17,6 +17,7 @@ import java.text.DateFormat;
import java.text.SimpleDateFormat; import java.text.SimpleDateFormat;
import java.util.*; import java.util.*;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentSkipListMap;
import java.util.concurrent.PriorityBlockingQueue; import java.util.concurrent.PriorityBlockingQueue;
/** /**
@@ -24,7 +25,6 @@ import java.util.concurrent.PriorityBlockingQueue;
* and parsed in by turning the byte arrays into useful data. There are two public static hashmaps * and parsed in by turning the byte arrays into useful data. There are two public static hashmaps
* that are threadsafe so the visualiser can always access the latest speed and position available * that are threadsafe so the visualiser can always access the latest speed and position available
* Created by kre39 on 23/04/17. * Created by kre39 on 23/04/17.
*
*/ */
public class StreamParser extends Thread{ public class StreamParser extends Thread{
@@ -37,8 +37,8 @@ public class StreamParser extends Thread{
private static boolean raceFinished = false; private static boolean raceFinished = false;
private static boolean streamStatus = false; private static boolean streamStatus = false;
private static long timeSinceStart = -1; private static long timeSinceStart = -1;
private static Map<Integer, Yacht> boats = new HashMap<>(); private static Map<Integer, Yacht> boats = new ConcurrentHashMap<>();
private static Map<Long, Yacht> boatsPos = new TreeMap<>(); private static Map<Long, Yacht> boatsPos = new ConcurrentSkipListMap<>();
private static double windDirection = 0; private static double windDirection = 0;
private static Long currentTimeLong; private static Long currentTimeLong;
private static String currentTimeString; private static String currentTimeString;
@@ -1,10 +1,7 @@
package seng302.server.messages; package seng302.server.messages;
import java.io.DataOutputStream;
import java.io.IOException; import java.io.IOException;
import java.nio.channels.Channels;
import java.nio.channels.SocketChannel; import java.nio.channels.SocketChannel;
import java.nio.channels.WritableByteChannel;
public class BoatLocationMessage extends Message { public class BoatLocationMessage extends Message {
private final int MESSAGE_SIZE = 56; private final int MESSAGE_SIZE = 56;
@@ -44,7 +41,7 @@ public class BoatLocationMessage extends Message {
public BoatLocationMessage(int sourceId, int sequenceNum, double latitude, double longitude, double heading, long boatSpeed){ public BoatLocationMessage(int sourceId, int sequenceNum, double latitude, double longitude, double heading, long boatSpeed){
boatSpeed /= 10; boatSpeed /= 10;
messageVersionNumber = 1; messageVersionNumber = 1;
time = System.currentTimeMillis() / 1000L; time = System.currentTimeMillis();
this.sourceId = sourceId; this.sourceId = sourceId;
this.sequenceNum = sequenceNum; this.sequenceNum = sequenceNum;
this.deviceType = DeviceType.RACING_YACHT; this.deviceType = DeviceType.RACING_YACHT;
+18
View File
@@ -128,6 +128,24 @@ Table
-fx-border-style: none; -fx-border-style: none;
} }
/**
Combo Box
*/
.combo-box-base {
-fx-background-color: #119796;
-fx-text-fill: white;
}
.combo-box-popup .list-view .list-cell:hover {
-fx-text-fill: white;
}
.combo-box .cell:selected {
-fx-text-fill: white;
}
/** /**
Remove scroll bars Remove scroll bars
*/ */
+3 -1
View File
@@ -56,9 +56,11 @@
</Text> </Text>
</children> </children>
</Pane> </Pane>
<Slider fx:id="annotationSlider" blockIncrement="1.0" layoutX="38.0" layoutY="527.0" majorTickUnit="1.0" max="3.0" minorTickCount="0" prefHeight="51.0" prefWidth="170.0" showTickLabels="true" showTickMarks="true" snapToTicks="true" styleClass="ui-slider" /> <Slider fx:id="annotationSlider" blockIncrement="1.0" layoutX="38.0" layoutY="527.0" majorTickUnit="1.0" max="2.0" minorTickCount="0" prefHeight="51.0" prefWidth="170.0" showTickLabels="true" showTickMarks="true" snapToTicks="true" styleClass="ui-slider" />
<Label layoutX="10.0" layoutY="499.0" text="Annotations" textFill="WHITE" /> <Label layoutX="10.0" layoutY="499.0" text="Annotations" textFill="WHITE" />
<Button fx:id="selectAnnotationBtn" layoutX="35.0" layoutY="578.0" mnemonicParsing="false" prefHeight="18.0" prefWidth="170.0" styleClass="blue-ui-btn" text="Select Annotations" textFill="WHITE" /> <Button fx:id="selectAnnotationBtn" layoutX="35.0" layoutY="578.0" mnemonicParsing="false" prefHeight="18.0" prefWidth="170.0" styleClass="blue-ui-btn" text="Select Annotations" textFill="WHITE" />
<Text fill="WHITE" layoutX="11.0" layoutY="649.0" strokeType="OUTSIDE" strokeWidth="0.0" text="Boat Selection" />
<ComboBox fx:id="boatSelectionComboBox" layoutX="37.0" layoutY="664.0" prefHeight="25.0" prefWidth="170.0" promptText="Select Boat" styleClass="combo-box-base" />
</children> </children>
</AnchorPane> </AnchorPane>
<AnchorPane fx:id="contentAnchorPane" prefHeight="960.0" prefWidth="1280.0" style="-fx-background-color: skyblue;" GridPane.columnIndex="1" GridPane.halignment="LEFT" GridPane.rowSpan="2147483647" GridPane.valignment="TOP"> <AnchorPane fx:id="contentAnchorPane" prefHeight="960.0" prefWidth="1280.0" style="-fx-background-color: skyblue;" GridPane.columnIndex="1" GridPane.halignment="LEFT" GridPane.rowSpan="2147483647" GridPane.valignment="TOP">