Implemented observer and strategy pattern in BoatAnnotations, now renamed AnnotationsBox.

Also implemented various other small fixes and further refactored code.

#refactor
This commit is contained in:
Calum
2017-07-25 20:45:27 +12:00
parent aad93d8913
commit 6242ab0b2e
12 changed files with 663 additions and 611 deletions
+23 -14
View File
@@ -1,9 +1,11 @@
package seng302.model; package seng302.model;
import javafx.beans.property.ReadOnlyDoubleProperty;
import javafx.beans.property.ReadOnlyDoubleWrapper;
import javafx.beans.property.ReadOnlyLongProperty;
import javafx.beans.property.ReadOnlyLongWrapper;
import javafx.scene.paint.Color; import javafx.scene.paint.Color;
import seng302.model.mark.Mark; import seng302.model.mark.Mark;
import seng302.model.stream.packets.StreamPacket;
import seng302.visualiser.controllers.RaceViewController;
import java.text.DateFormat; import java.text.DateFormat;
import java.text.SimpleDateFormat; import java.text.SimpleDateFormat;
@@ -30,15 +32,14 @@ public class Boat {
private Integer boatStatus; private Integer boatStatus;
private Integer legNumber = 0; private Integer legNumber = 0;
private Integer position = 0; private Integer position = 0;
private Integer penaltiesAwarded;
private Integer penaltiesServed;
private Long estimateTimeAtFinish; private Long estimateTimeAtFinish;
private Long markRoundTime;
private Double lat; private Double lat;
private Double lon; private Double lon;
private Double heading; private Double heading;
private double velocity; private ReadOnlyDoubleWrapper velocity = new ReadOnlyDoubleWrapper();
private Long timeTillNext; private ReadOnlyLongWrapper timeTillNext = new ReadOnlyLongWrapper();
private Long markRoundTime; private ReadOnlyLongWrapper timeSinceLastMark = new ReadOnlyLongWrapper();
// Mark rounding // Mark rounding
private Mark lastMarkRounded; private Mark lastMarkRounded;
@@ -94,8 +95,8 @@ public class Boat {
this.legNumber = legNumber; this.legNumber = legNumber;
} }
public void setEstimateTimeAtNextMark(Long estimateTimeAtNextMark) { public void setEstimateTimeTillNextMark(Long estimateTimeAtNextMark) {
timeTillNext = estimateTimeAtNextMark; timeTillNext.set(estimateTimeAtNextMark);
} }
public String getEstimateTimeAtFinish() { public String getEstimateTimeAtFinish() {
@@ -124,7 +125,7 @@ public class Boat {
} }
public void setVelocity(double velocity) { public void setVelocity(double velocity) {
this.velocity = velocity; this.velocity.set(velocity);
} }
@@ -132,12 +133,12 @@ public class Boat {
this.markRoundTime = markRoundingTime; this.markRoundTime = markRoundingTime;
} }
public double getVelocity() { public ReadOnlyDoubleProperty getVelocityProperty() {
return velocity; return velocity.getReadOnlyProperty();
} }
public Long getTimeTillNext() { public ReadOnlyLongProperty timeTillNextProperty() {
return timeTillNext; return timeTillNext.getReadOnlyProperty();
} }
public Long getMarkRoundTime() { public Long getMarkRoundTime() {
@@ -189,4 +190,12 @@ public class Boat {
return boatName; return boatName;
} }
public void setTimeSinceLastMark (long timeSinceLastMark) {
this.timeSinceLastMark.set(timeSinceLastMark);
}
public ReadOnlyLongProperty timeSinceLastMarkProperty () {
return timeSinceLastMark.getReadOnlyProperty();
}
} }
@@ -0,0 +1,72 @@
package seng302.model;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.TimeZone;
import seng302.model.stream.parsers.RaceStartData;
import seng302.model.stream.parsers.RaceStatusData;
/**
* Class for storing race data that does not relate to specific vessels or marks such as time or wind.
* Calculates the state of critical race attributes when relevant data is added.
*/
public class RaceState {
// private final DateFormat DATE_TIME_FORMAT = new SimpleDateFormat("dd/MM/yyyy HH:mm:ss");
private final DateFormat DATE_TIME_FORMAT = new SimpleDateFormat("HH:mm:ss");
private double windSpeed;
private double windDirection;
private long raceTime;
private long expectedStartTime;
private boolean isRaceStarted;
// long timeTillStart;
public RaceState() {
}
public void updateState (RaceStatusData data) {
this.windSpeed = data.getWindSpeed();
this.windDirection = data.getWindDirection();
this.raceTime = data.getCurrentTime();
this.expectedStartTime = data.getExpectedStartTime();
this.isRaceStarted = data.isRaceStarted();
}
public void setTimeZone (TimeZone timeZone) {
DATE_TIME_FORMAT.setTimeZone(timeZone);
}
public void updateState (RaceStartData data) {
// this.timeTillStart = data.getRaceStartTime();
System.out.println(data.getRaceStartTime());
}
public String getRaceTimeStr () {
return DATE_TIME_FORMAT.format(raceTime);
}
public long getTimeTillStart () {
return (expectedStartTime - raceTime) / 1000;
}
public double getWindSpeed() {
return windSpeed;
}
public double getWindDirection() {
return windDirection;
}
public long getRaceTime() {
return raceTime;
}
public long getExpectedStartTime() {
return expectedStartTime;
}
public boolean isRaceStarted () {
return isRaceStarted;
}
}
@@ -1,16 +0,0 @@
package seng302.model;
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.LongProperty;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.beans.property.SimpleLongProperty;
/**
* Class for storing race data that does not relate to specific vessels or marks such as time or wind
*/
public class RaceStatus {
double windSpeed;
double windDirection;
long raceTime;
}
@@ -38,20 +38,6 @@ public class StreamParser {
return heartbeat; return heartbeat;
} }
private static String getTimeZoneString(RegattaXMLData regattaXML) {
Integer offset = regattaXML.getUtcOffset();
StringBuilder utcOffset = new StringBuilder();
utcOffset.append("GMT");
if (offset > 0) {
utcOffset.append("+");
utcOffset.append(offset);
} else if (offset < 0) {
utcOffset.append("-");
utcOffset.append(offset);
}
return utcOffset.toString();
}
/** /**
* Extracts the useful race status data from race status type packets. This method will also * Extracts the useful race status data from race status type packets. This method will also
* print to the console the current state of the race (if it has started/finished or is about to * print to the console the current state of the race (if it has started/finished or is about to
@@ -112,7 +98,7 @@ public class StreamParser {
// boat.setPenaltiesServed((int) payload[31 + (i * 20)]); // boat.setPenaltiesServed((int) payload[31 + (i * 20)]);
Long estTimeAtNextMark = bytesToLong( Long estTimeAtNextMark = bytesToLong(
Arrays.copyOfRange(payload, 32 + (i * 20), 38 + (i * 20))); Arrays.copyOfRange(payload, 32 + (i * 20), 38 + (i * 20)));
// boat.setEstimateTimeAtNextMark(estTimeAtNextMark); // boat.setEstimateTimeTillNextMark(estTimeAtNextMark);
Long estTimeAtFinish = bytesToLong( Long estTimeAtFinish = bytesToLong(
Arrays.copyOfRange(payload, 38 + (i * 20), 44 + (i * 20))); Arrays.copyOfRange(payload, 38 + (i * 20), 44 + (i * 20)));
int leg = (int) payload[29 + (i * 20)]; int leg = (int) payload[29 + (i * 20)];
+108 -108
View File
@@ -1,11 +1,13 @@
package seng302.visualiser; package seng302.visualiser;
import java.io.IOException; import java.io.IOException;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Comparator; import java.util.Comparator;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.concurrent.PriorityBlockingQueue;
import javafx.animation.AnimationTimer; import javafx.animation.AnimationTimer;
import javafx.beans.property.ReadOnlyBooleanProperty; import javafx.beans.property.ReadOnlyBooleanProperty;
import javafx.collections.ObservableList; import javafx.collections.ObservableList;
@@ -21,8 +23,9 @@ import javafx.scene.paint.Color;
import javafx.scene.shape.Polygon; import javafx.scene.shape.Polygon;
import javafx.scene.text.Text; import javafx.scene.text.Text;
import seng302.model.Limit; import seng302.model.Limit;
import seng302.visualiser.fxObjects.BoatGroup; import seng302.visualiser.fxObjects.AnnotationBox;
import seng302.visualiser.fxObjects.MarkGroup; import seng302.visualiser.fxObjects.BoatObject;
import seng302.visualiser.fxObjects.MarkObject;
import seng302.model.Colors; import seng302.model.Colors;
import seng302.model.Boat; import seng302.model.Boat;
import seng302.model.map.Boundary; import seng302.model.map.Boundary;
@@ -32,10 +35,6 @@ import seng302.model.mark.Mark;
import seng302.model.mark.MarkType; import seng302.model.mark.MarkType;
import seng302.model.mark.SingleMark; import seng302.model.mark.SingleMark;
import seng302.model.stream.parsers.StreamParser; import seng302.model.stream.parsers.StreamParser;
import seng302.model.stream.parsers.xml.XMLParser;
import seng302.model.stream.parsers.xml.XMLParser.RaceXMLObject.Limit;
import seng302.model.stream.parsers.xml.XMLParser.RaceXMLObject.Participant;
import seng302.model.stream.parsers.PositionUpdateData;
import seng302.utilities.GeoPoint; import seng302.utilities.GeoPoint;
import seng302.utilities.GeoUtility; import seng302.utilities.GeoUtility;
@@ -65,8 +64,9 @@ public class GameView extends Pane {
private double metersPerPixelX; private double metersPerPixelX;
private double metersPerPixelY; private double metersPerPixelY;
private List<MarkGroup> markGroups = new ArrayList<>(); private Map<SingleMark, MarkObject> markObjects = new HashMap<>();
private List<BoatGroup> boatGroups = new ArrayList<>(); private Map<Boat, BoatObject> boatObjects = new HashMap<>();
private List<AnnotationBox> annotations = new ArrayList<>();
private Text fpsDisplay = new Text(); private Text fpsDisplay = new Text();
@@ -130,17 +130,14 @@ public class GameView extends Pane {
}; };
} }
void initializeCanvas() { public void initializeCanvas() {
fitMarksToCanvas();
drawGoogleMap(); drawGoogleMap();
fpsDisplay.setLayoutX(5); fpsDisplay.setLayoutX(5);
fpsDisplay.setLayoutY(20); fpsDisplay.setLayoutY(20);
fpsDisplay.setStrokeWidth(2); fpsDisplay.setStrokeWidth(2);
gameObjects.add(fpsDisplay); gameObjects.add(fpsDisplay);
gameObjects.add(raceBorder); gameObjects.add(raceBorder);
initializeMarks();
initializeBoats();
this.widthProperty().addListener(resize -> { this.widthProperty().addListener(resize -> {
canvasWidth = this.getWidth(); canvasWidth = this.getWidth();
canvasHeight = this.getHeight(); canvasHeight = this.getHeight();
@@ -206,14 +203,12 @@ public class GameView extends Pane {
* same, so they do not have things such as a type and must be derived from the number of marks * same, so they do not have things such as a type and must be derived from the number of marks
* in a compound mark etc.. * in a compound mark etc..
*/ */
private void addRaceBorder() { public void updateBorder(List<Limit> border) {
XMLParser.RaceXMLObject raceXMLObject = StreamParser.getXmlObject().getRaceXML();
ArrayList<Limit> courseLimits = raceXMLObject.getCourseLimit();
raceBorder.setStroke(new Color(0.0f, 0.0f, 0.74509807f, 1)); raceBorder.setStroke(new Color(0.0f, 0.0f, 0.74509807f, 1));
raceBorder.setStrokeWidth(3); raceBorder.setStrokeWidth(3);
raceBorder.setFill(new Color(0,0,0,0)); raceBorder.setFill(new Color(0,0,0,0));
List<Double> boundaryPoints = new ArrayList<>(); List<Double> boundaryPoints = new ArrayList<>();
for (Limit limit : courseLimits) { for (Limit limit : border) {
Point2D location = findScaledXY(limit.getLat(), limit.getLng()); Point2D location = findScaledXY(limit.getLat(), limit.getLng());
boundaryPoints.add(location.getX()); boundaryPoints.add(location.getX());
boundaryPoints.add(location.getY()); boundaryPoints.add(location.getY());
@@ -222,112 +217,114 @@ public class GameView extends Pane {
} }
private void updateGroups() { private void updateGroups() {
for (BoatGroup boatGroup : boatGroups) { boatObjects.forEach((boat, boatObject) -> {});
// some raceObjects will have multiple ID's (for instance gate marks) markObjects.forEach((mark, markObject) -> {});
//checking if the current "ID" has any updates associated with it
if (StreamParser.boatLocations.containsKey(boatGroup.getRaceId())) {
if (boatGroup.isStopped()) {
updateBoatGroup(boatGroup);
}
}
boatGroup.move();
}
for (MarkGroup markGroup : markGroups) {
for (Long id : markGroup.getRaceIds()) {
if (StreamParser.markLocations.containsKey(id)) {
updateMarkGroup(id, markGroup);
}
}
}
checkForCourseChanges();
} }
private void checkForCourseChanges() { // private void updateBoatGroup(BoatGroup boatGroup) {
if (StreamParser.isNewRaceXmlReceived()){ //// PriorityBlockingQueue<PositionUpdateData> movementQueue = StreamParser.boatLocations.get(boatGroup.getRaceId());
addRaceBorder(); //// // giving the movementQueue a 5 packet buffer to account for slightly out of order packets
} //// if (movementQueue.size() > 0) {
} //// try {
//// PositionUpdateData positionPacket = movementQueue.take();
private void updateBoatGroup(BoatGroup boatGroup) { // Point2D p2d = findScaledXY(positionPacket.getLat(), positionPacket.getLon());
// PriorityBlockingQueue<PositionUpdateData> movementQueue = StreamParser.boatLocations.get(boatGroup.getRaceId()); //// double heading = 360.0 / 0xffff * positionPacket.getHeading();
// // giving the movementQueue a 5 packet buffer to account for slightly out of order packets // boatGroup.setDestination(
// p2d.getX(), p2d.getY(), heading, positionPacket.getGroundSpeed(),
// positionPacket.getTimeValid(), frameRate);
//// } catch (InterruptedException e){
//// e.printStackTrace();
//// }
////// }
//// }
// }
//
// private void updateMarkGroup (long raceId, MarkGroup markGroup) {
// PriorityBlockingQueue<PositionUpdateData> movementQueue = StreamParser.markLocations.get(raceId);
// if (movementQueue.size() > 0){ // if (movementQueue.size() > 0){
// try { // try {
// PositionUpdateData positionPacket = movementQueue.take(); // PositionUpdateData positionPacket = movementQueue.take();
Point2D p2d = findScaledXY(positionPacket.getLat(), positionPacket.getLon()); // Point2D p2d = findScaledXY(positionPacket.getLat(), positionPacket.getLon());
// double heading = 360.0 / 0xffff * positionPacket.getHeading(); // markGroup.moveMarkTo(p2d.getX(), p2d.getY(), raceId);
boatGroup.setDestination(
p2d.getX(), p2d.getY(), heading, positionPacket.getGroundSpeed(),
positionPacket.getTimeValid(), frameRate);
// } catch (InterruptedException e) { // } catch (InterruptedException e) {
// e.printStackTrace(); // e.printStackTrace();
// } // }
//// }
// } // }
} // }
private void updateMarkGroup (long raceId, MarkGroup markGroup) {
PriorityBlockingQueue<PositionUpdateData> movementQueue = StreamParser.markLocations.get(raceId);
if (movementQueue.size() > 0){
try {
PositionUpdateData positionPacket = movementQueue.take();
Point2D p2d = findScaledXY(positionPacket.getLat(), positionPacket.getLon());
markGroup.moveMarkTo(p2d.getX(), p2d.getY(), raceId);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
/** /**
* Draws all the boats. * Draws all the boats.
*/ */
private void initializeBoats() { public void setBoats(List<Boat> boats) {
Map<Integer, Boat> boats = StreamParser.getBoats(); Group annotationsGroup = new Group();
Group wakes = new Group(); Group wakesGroup = new Group();
Group trails = new Group(); Group boatObjectGroup = new Group();
Group annotations = new Group();
ArrayList<Participant> participants = StreamParser.getXmlObject().getRaceXML() BoatObject newObject;
.getParticipants(); for (Boat boat : boats) {
ArrayList<Integer> participantIDs = new ArrayList<>(); newObject = new BoatObject();
for (Participant p : participants) { // newObject.bindBoat(boat);
participantIDs.add(p.getsourceID()); newObject.setFill(Colors.getColor());
createAnnotationBox(boat);
}
// Group wakes = new Group();
// Group trails = new Group();
// Group annotationsGroup = new Group();
//
// gameObjects.addAll(trails);
// gameObjects.addAll(wakes);
annotationsGroup.getChildren().addAll(annotations);
gameObjects.addAll(annotationsGroup);
gameObjects.addAll(boatObjects.values());
} }
for (Boat boat : boats.values()) { private AnnotationBox createAnnotationBox (Boat boat) {
if (participantIDs.contains(boat.getSourceID())) { AnnotationBox newAnnotation;
boat.setColour(Colors.getColor()); newAnnotation = new AnnotationBox();
BoatGroup boatGroup = new BoatGroup(boat, boat.getColour()); newAnnotation.addAnnotation("name", boat.getShortName());
boatGroups.add(boatGroup); // newAnnotation.addAnnotation("country", boat.getCountry());
trails.getChildren().add(boatGroup.getTrail()); newAnnotation.addAnnotation(
wakes.getChildren().add(boatGroup.getWake()); "velocity",
annotations.getChildren().add(boatGroup.getAnnotations()); boat.getVelocityProperty(),
(velocity) -> String.format("%.2f ms", velocity.doubleValue())
);
newAnnotation.addAnnotation(
"nextMark",
boat.timeTillNextProperty(),
(time) -> {
DateFormat format = new SimpleDateFormat("mm:ss");
return format.format(time);
} }
);
newAnnotation.addAnnotation(
"lastMark",
boat.timeTillNextProperty(),
(time) -> {
DateFormat format = new SimpleDateFormat("mm:ss");
return format.format(time);
} }
gameObjects.addAll(trails); );
gameObjects.addAll(wakes); annotations.add(newAnnotation);
gameObjects.addAll(annotations); return newAnnotation;
gameObjects.addAll(boatGroups);
} }
private void initializeMarks() { public void updateCourse(List<Mark> course) {
List<Mark> allMarks = StreamParser.getXmlObject().getRaceXML().getNonDupCompoundMarks(); for (Mark mark : course) {
for (Mark mark : allMarks) {
if (mark.getMarkType() == MarkType.SINGLE_MARK) { if (mark.getMarkType() == MarkType.SINGLE_MARK) {
SingleMark sMark = (SingleMark) mark; SingleMark sMark = (SingleMark) mark;
MarkGroup markGroup = new MarkGroup(sMark, findScaledXY(sMark)); MarkObject markObject = new MarkObject(sMark, findScaledXY(sMark));
markGroups.add(markGroup); markObjects.put(sMark, markObject);
} else { } else {
GateMark gMark = (GateMark) mark; GateMark gMark = (GateMark) mark;
MarkGroup markGroup = new MarkGroup(gMark, findScaledXY(gMark.getSingleMark1()), MarkObject markObject = new MarkObject(gMark, findScaledXY(gMark.getSingleMark1()),
findScaledXY(gMark.getSingleMark2())); //should be 2 objects in the list. findScaledXY(gMark.getSingleMark2())); //should be 2 objects in the list.
markGroups.add(markGroup); // markObjects.put(markObject.);
} }
} }
gameObjects.addAll(markGroups); // gameObjects.addAll(markObjects);
} }
private void drawFps(int fps){ private void drawFps(int fps){
@@ -340,12 +337,11 @@ public class GameView extends Pane {
*/ */
private void fitMarksToCanvas() { private void fitMarksToCanvas() {
//Check is called once to avoid unnecessarily change the course limits once the race is running //Check is called once to avoid unnecessarily change the course limits once the race is running
StreamParser.isNewRaceXmlReceived();
findMinMaxPoint(); findMinMaxPoint();
double minLonToMaxLon = scaleRaceExtremities(); double minLonToMaxLon = scaleRaceExtremities();
calculateReferencePointLocation(minLonToMaxLon); calculateReferencePointLocation(minLonToMaxLon);
//givePointsXY(); //givePointsXY();
addRaceBorder(); // updateBorder();
} }
@@ -356,9 +352,9 @@ public class GameView extends Pane {
*/ */
private void findMinMaxPoint() { private void findMinMaxPoint() {
List<Limit> sortedPoints = new ArrayList<>(); List<Limit> sortedPoints = new ArrayList<>();
for (Limit limit : raceData) { // for (Limit limit : ) {
sortedPoints.add(limit); // sortedPoints.add(limit);
} // }
sortedPoints.sort(Comparator.comparingDouble(Limit::getLat)); sortedPoints.sort(Comparator.comparingDouble(Limit::getLat));
Limit minLatMark = sortedPoints.get(0); Limit minLatMark = sortedPoints.get(0);
Limit maxLatMark = sortedPoints.get(sortedPoints.size()-1); Limit maxLatMark = sortedPoints.get(sortedPoints.size()-1);
@@ -503,8 +499,14 @@ public class GameView extends Pane {
public void setAnnotationVisibilities (boolean teamName, boolean velocity, boolean estTime, public void setAnnotationVisibilities (boolean teamName, boolean velocity, boolean estTime,
boolean legTime, boolean trail, boolean wake) { boolean legTime, boolean trail, boolean wake) {
for (BoatGroup boatGroup : boatGroups) { for (BoatObject boatObject : boatObjects.values()) {
boatGroup.setVisibility(teamName, velocity, estTime, legTime, trail, wake); boatObject.setVisibility(teamName, velocity, estTime, legTime, trail, wake);
}
for (AnnotationBox ag : annotations) {
ag.setAnnotationVisibility("name", teamName);
ag.setAnnotationVisibility("velocity", velocity);
ag.setAnnotationVisibility("nextMark", estTime);
ag.setAnnotationVisibility("lastMark", legTime);
} }
} }
@@ -516,8 +518,10 @@ public class GameView extends Pane {
return fpsDisplay.visibleProperty(); return fpsDisplay.visibleProperty();
} }
public void selectBoat (int boatId) { public void selectBoat (Boat selectedBoat) {
boatObjects.forEach((boat, group) ->
group.setIsSelected(boat == selectedBoat)
);
} }
public void pauseRace () { public void pauseRace () {
@@ -527,8 +531,4 @@ public class GameView extends Pane {
public void startRace () { public void startRace () {
timer.start(); timer.start();
} }
public void updateBorder (List<Limit> courseLimit) {
}
} }
@@ -1,16 +1,21 @@
package seng302.visualiser.controllers; package seng302.visualiser.controllers;
import com.sun.javafx.collections.SortableList;
import java.util.concurrent.TimeUnit;
import javafx.animation.KeyFrame; import javafx.animation.KeyFrame;
import javafx.animation.Timeline; import javafx.animation.Timeline;
import javafx.collections.FXCollections; import javafx.collections.FXCollections;
import javafx.collections.ObservableList; import javafx.collections.ObservableList;
import javafx.collections.transformation.SortedList;
import javafx.fxml.FXML; import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader; import javafx.fxml.FXMLLoader;
import javafx.geometry.Point2D; import javafx.geometry.Point2D;
import javafx.scene.Scene; import javafx.scene.Scene;
import javafx.scene.chart.LineChart; import javafx.scene.chart.LineChart;
import javafx.scene.chart.NumberAxis; import javafx.scene.chart.NumberAxis;
import javafx.scene.chart.XYChart; import javafx.scene.chart.XYChart;
import javafx.scene.chart.XYChart.Data;
import javafx.scene.chart.XYChart.Series; import javafx.scene.chart.XYChart.Series;
import javafx.scene.control.Button; import javafx.scene.control.Button;
import javafx.scene.control.CheckBox; import javafx.scene.control.CheckBox;
@@ -35,8 +40,8 @@ import seng302.visualiser.controllers.annotations.Annotation;
import seng302.visualiser.controllers.annotations.ImportantAnnotationController; import seng302.visualiser.controllers.annotations.ImportantAnnotationController;
import seng302.visualiser.controllers.annotations.ImportantAnnotationDelegate; import seng302.visualiser.controllers.annotations.ImportantAnnotationDelegate;
import seng302.visualiser.controllers.annotations.ImportantAnnotationsState; import seng302.visualiser.controllers.annotations.ImportantAnnotationsState;
import seng302.visualiser.fxObjects.BoatGroup; import seng302.visualiser.fxObjects.BoatObject;
import seng302.visualiser.fxObjects.MarkGroup; import seng302.visualiser.fxObjects.MarkObject;
import seng302.model.*; import seng302.model.*;
import seng302.model.mark.GateMark; import seng302.model.mark.GateMark;
import seng302.model.mark.Mark; import seng302.model.mark.Mark;
@@ -45,15 +50,14 @@ import seng302.model.stream.parsers.StreamParser;
import java.io.IOException; import java.io.IOException;
import java.util.*; import java.util.*;
import seng302.model.stream.parsers.xml.XMLParser.RaceXMLObject.Participant;
/** /**
* Created by ptg19 on 29/03/17. * Controller class that manages the display of a race
*/ */
public class RaceViewController extends Thread implements ImportantAnnotationDelegate { public class RaceViewController extends Thread implements ImportantAnnotationDelegate {
@FXML @FXML
private LineChart raceSparkLine; private LineChart<String, Double> raceSparkLine;
@FXML @FXML
private NumberAxis sparklineYAxis; private NumberAxis sparklineYAxis;
@FXML @FXML
@@ -75,15 +79,14 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel
//Race Data //Race Data
private Map<Integer, Boat> participants; private Map<Integer, Boat> participants;
private Map<Integer, Mark> course; private Map<Integer, Mark> markers;
private RaceXMLData raceData; private RaceXMLData courseData;
private GameView gameView; private GameView gameView;
private RaceState raceState;
private Timeline timerTimeline; private Timeline timerTimeline;
private Stage stage;
private HashMap<Integer, Series<String, Double>> sparkLineData = new HashMap<>(); private HashMap<Integer, Series<String, Double>> sparkLineData = new HashMap<>();
private ImportantAnnotationsState importantAnnotations; private ImportantAnnotationsState importantAnnotations;
private Boat selectedBoat;
public void initialize() { public void initialize() {
// Load a default important annotation state // Load a default important annotation state
@@ -96,20 +99,26 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel
raceSparkLine.getYAxis().setAutoRanging(false); raceSparkLine.getYAxis().setAutoRanging(false);
sparklineYAxis.setTickMarkVisible(false); sparklineYAxis.setTickMarkVisible(false);
selectAnnotationBtn.setOnAction(event -> loadSelectAnnotationView());
}
public void loadRace (Map<Integer, Boat> participants, RaceXMLData raceData, RaceState raceState) {
this.participants = participants;
this.courseData = raceData;
this.markers = raceData.getCompoundMarks();
this.raceState = raceState;
initializeUpdateTimer(); initializeUpdateTimer();
initialiseFPSCheckBox(); initialiseFPSCheckBox();
initialiseAnnotationSlider(); initialiseAnnotationSlider();
initialiseBoatSelectionComboBox(); initialiseBoatSelectionComboBox();
selectAnnotationBtn.setOnAction(event -> loadSelectAnnotationView());
}
public void loadRace (Map<Integer, Boat> participants, RaceXMLData raceData) {
this.participants = participants;
this.raceData = raceData;
this.course = raceData.getCompoundMarks();
gameView = new GameView(); gameView = new GameView();
gameView.setBoats(new ArrayList<>(participants.values()));
gameView.updateBorder(raceData.getCourseLimit());
gameView.updateCourse(new ArrayList<>(raceData.getCompoundMarks().values()));
gameView.startRace(); gameView.startRace();
} }
/** /**
@@ -169,7 +178,6 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel
if (n == 2) { if (n == 2) {
return "All"; return "All";
} }
return "All"; return "All";
} }
@@ -189,7 +197,7 @@ 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(2); annotationSlider.setValue(2);
@@ -202,10 +210,11 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel
private void updateSparkLine(){ private void updateSparkLine(){
// Collect the racing boats that aren't already in the chart // Collect the racing boats that aren't already in the chart
List<Boat> sparkLineCandidates = new ArrayList<>(); List<Boat> sparkLineCandidates = new ArrayList<>();
participants.forEach((id, boat) ->{ // participants.forEach((id, boat) ->{
if (!sparkLineData.containsKey(id) && boat.getPosition() != null && !boat.getPosition().equals("-")) // if (!sparkLineData.containsKey(id) && boat.getPosition() != null && !boat.getPosition().equals("-"))
sparkLineCandidates.add(boat); // sparkLineCandidates.add(boat);
}); // });
participants.forEach((id, boat) -> sparkLineCandidates.add(boat));
sparklineYAxis.setUpperBound(participants.size() + 1); sparklineYAxis.setUpperBound(participants.size() + 1);
@@ -213,13 +222,18 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel
sparkLineCandidates.stream().filter(yacht -> yacht.getPosition() != null).forEach(yacht -> { sparkLineCandidates.stream().filter(yacht -> yacht.getPosition() != null).forEach(yacht -> {
Series<String, Double> yachtData = new Series<>(); Series<String, Double> yachtData = new Series<>();
yachtData.setName(yacht.getSourceID().toString()); yachtData.setName(yacht.getSourceID().toString());
yachtData.getData().add(new XYChart.Data<>(Integer.toString(yacht.getLegNumber()), 1 + participants.size() - Double.parseDouble(yacht.getPosition()))); yachtData.getData().add(
new XYChart.Data<>(
Integer.toString(yacht.getLegNumber()),
1.0 + participants.size() - yacht.getPosition()
)
);
sparkLineData.put(yacht.getSourceID(), yachtData); sparkLineData.put(yacht.getSourceID(), yachtData);
}); });
// Lambda function to sort the series in order of leg (later legs shown more to the right) // Lambda function to sort the series in order of leg (later legs shown more to the right)
List<XYChart.Series<String, Double>> positions = new ArrayList<>(sparkLineData.values()); List<XYChart.Series<String, Double>> positions = new ArrayList<>(sparkLineData.values());
Collections.sort(positions, (o1, o2) -> { positions.sort((o1, o2) -> {
Integer leg1 = Integer.parseInt(o1.getData().get(o1.getData().size()-1).getXValue()); Integer leg1 = Integer.parseInt(o1.getData().get(o1.getData().size()-1).getXValue());
Integer leg2 = Integer.parseInt(o2.getData().get(o2.getData().size()-1).getXValue()); Integer leg2 = Integer.parseInt(o2.getData().get(o2.getData().size()-1).getXValue());
if (leg2 < leg1){ if (leg2 < leg1){
@@ -247,14 +261,22 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel
* @param legNumber the leg number that the position will be assigned to * @param legNumber the leg number that the position will be assigned to
*/ */
public void updateYachtPositionSparkline(Boat boat, Integer legNumber){ public void updateYachtPositionSparkline(Boat boat, Integer legNumber){
XYChart.Series<String, Double> positionData = sparkLineData.get(boat.getSourceID()); for (XYChart.Series<String, Double> positionData : sparkLineData.values()) {
positionData.getData().add( positionData.getData().add(
new XYChart.Data<>( new Data<>(
Integer.toString(legNumber), Integer.toString(legNumber),
1 + participants.size() - Double.parseDouble(boat.getPosition()) 1.0 + participants.size() - boat.getPosition()
) )
); );
} }
// XYChart.Series<String, Double> positionData = sparkLineData.get(boat.getSourceID());
// positionData.getData().add(
// new XYChart.Data<>(
// Integer.toString(legNumber),
// 1.0 + participants.size() - boat.getPosition()
// )
// );
}
/** /**
@@ -306,10 +328,10 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel
* @param bg The BoatGroup to find the next mark of * @param bg The BoatGroup to find the next mark of
* @return The next Mark or null if none found * @return The next Mark or null if none found
*/ */
private Mark getNextMark(BoatGroup bg) { private Mark getNextMark(BoatObject bg) {
Integer legNumber = bg.getBoat().getLegNumber(); Integer legNumber = bg.getBoat().getLegNumber();
List<Corner> markSequence = raceData.getMarkSequence(); List<Corner> markSequence = courseData.getMarkSequence();
if (legNumber == 0) { if (legNumber == 0) {
return null; return null;
@@ -319,7 +341,7 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel
for (Corner corner : markSequence) { for (Corner corner : markSequence) {
if (legNumber + 2 == corner.getSeqID()) { if (legNumber + 2 == corner.getSeqID()) {
return raceData.getCompoundMarks().get(corner.getCompoundMarkID()); return courseData.getCompoundMarks().get(corner.getCompoundMarkID());
} }
} }
return null; return null;
@@ -330,8 +352,8 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel
* Updates the wind direction arrow and text as from info from the StreamParser * Updates the wind direction arrow and text as from info from the StreamParser
*/ */
private void updateWindDirection() { private void updateWindDirection() {
windDirectionText.setText(String.format("%.1f°", StreamParser.getWindDirection())); windDirectionText.setText(String.format("%.1f°", raceState.getWindDirection()));
windArrowText.setRotate(StreamParser.getWindDirection()); windArrowText.setRotate(raceState.getWindDirection());
} }
@@ -339,26 +361,14 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel
* Updates the clock for the race * Updates the clock for the race
*/ */
private void updateRaceTime() { private void updateRaceTime() {
if (StreamParser.isRaceFinished()) { if (!raceState.isRaceStarted()) {
timerLabel.setFill(Color.RED); timerLabel.setFill(Color.RED);
timerLabel.setText("Race Finished!"); timerLabel.setText("Race Finished!");
} else { } else {
timerLabel.setText(getTimeSinceStartOfRace()); timerLabel.setText(raceState.getRaceTimeStr());
} }
} }
/**
* 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<Boat> observableBoats = FXCollections.observableArrayList();
observableBoats.addAll(participants.values());
boatSelectionComboBox.setItems(observableBoats);
}
/** /**
* Updates the order of the boats as from the StreamParser and sets them in the boat order * Updates the order of the boats as from the StreamParser and sets them in the boat order
* section * section
@@ -369,10 +379,10 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel
positionVbox.getStylesheets().add(getClass().getResource("/css/master.css").toString()); positionVbox.getStylesheets().add(getClass().getResource("/css/master.css").toString());
// list of racing boat id // list of racing boat id
List<Boat> sorted = new ArrayList<>(participants.values());
sorted.sort(Comparator.comparingInt(Boat::getPosition));
if (StreamParser.isRaceStarted()) { for (Boat boat : sorted) {
for (Boat boat : StreamParser.getBoatsPos().values()) {
if (participantIDs.contains(boat.getSourceID())) { // check if the boat is racing
if (boat.getBoatStatus() == 3) { // 3 is finish status if (boat.getBoatStatus() == 3) { // 3 is finish status
Text textToAdd = new Text(boat.getPosition() + ". " + Text textToAdd = new Text(boat.getPosition() + ". " +
boat.getShortName() + " (Finished)"); boat.getShortName() + " (Finished)");
@@ -387,81 +397,78 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel
positionVbox.getChildren().add(textToAdd); positionVbox.getChildren().add(textToAdd);
} }
} }
} // participants.forEach((id, boat) ->{
} else { // Text textToAdd = new Text(boat.getPosition() + ". " +
participants.forEach((id, boat) ->{ // boat.getShortName() + " ");
Text textToAdd = new Text(boat.getPosition() + ". " + // textToAdd.setFill(Paint.valueOf("#d3d3d3"));
boat.getShortName() + " "); // textToAdd.setStyle("");
textToAdd.setFill(Paint.valueOf("#d3d3d3")); // positionVbox.getChildren().add(textToAdd);
textToAdd.setStyle(""); // });
positionVbox.getChildren().add(textToAdd);
});
}
} }
private void updateLaylines(BoatGroup bg) { // private void updateLaylines(BoatObject bg) {
//
Mark nextMark = getNextMark(bg); // Mark nextMark = getNextMark(bg);
Boolean isUpwind = null; // Boolean isUpwind = null;
// Can only calc leg direction if there is a next mark and it is a gate mark // // Can only calc leg direction if there is a next mark and it is a gate mark
if (nextMark != null) { // if (nextMark != null) {
if (nextMark instanceof GateMark) { // if (nextMark instanceof GateMark) {
if (bg.isUpwindLeg(gameViewController, nextMark)) { // if (bg.isUpwindLeg(gameViewController, nextMark)) {
isUpwind = true; // isUpwind = true;
} else { // } else {
isUpwind = false; // isUpwind = false;
} // }
//
for(MarkGroup mg : gameViewController.getMarkGroups()) { // for(MarkObject mg : gameViewController.getMarkGroups()) {
//
mg.removeLaylines(); // mg.removeLaylines();
//
if (mg.getMainMark().getId() == nextMark.getId()) { // if (mg.getMainMark().getId() == nextMark.getId()) {
//
SingleMark singleMark1 = ((GateMark) nextMark).getSingleMark1(); // SingleMark singleMark1 = ((GateMark) nextMark).getSingleMark1();
SingleMark singleMark2 = ((GateMark) nextMark).getSingleMark2(); // SingleMark singleMark2 = ((GateMark) nextMark).getSingleMark2();
Point2D markPoint1 = gameViewController // Point2D markPoint1 = gameViewController
.findScaledXY(singleMark1.getLatitude(), singleMark1.getLongitude()); // .findScaledXY(singleMark1.getLatitude(), singleMark1.getLongitude());
Point2D markPoint2 = gameViewController // Point2D markPoint2 = gameViewController
.findScaledXY(singleMark2.getLatitude(), singleMark2.getLongitude()); // .findScaledXY(singleMark2.getLatitude(), singleMark2.getLongitude());
HashMap<Double, Double> angleAndSpeed; // HashMap<Double, Double> angleAndSpeed;
if (isUpwind) { // if (isUpwind) {
angleAndSpeed = PolarTable.getOptimalUpwindVMG(StreamParser.getWindSpeed()); // angleAndSpeed = PolarTable.getOptimalUpwindVMG(StreamParser.getWindSpeed());
} else { // } else {
angleAndSpeed = PolarTable.getOptimalDownwindVMG(StreamParser.getWindSpeed()); // angleAndSpeed = PolarTable.getOptimalDownwindVMG(StreamParser.getWindSpeed());
} // }
//
Double resultingAngle = angleAndSpeed.keySet().iterator().next(); // Double resultingAngle = angleAndSpeed.keySet().iterator().next();
//
//
Point2D boatCurrentPos = new Point2D(bg.getBoatLayoutX(), bg.getBoatLayoutY()); // Point2D boatCurrentPos = new Point2D(bg.getBoatLayoutX(), bg.getBoatLayoutY());
Point2D gateMidPoint = markPoint1.midpoint(markPoint2); // Point2D gateMidPoint = markPoint1.midpoint(markPoint2);
Integer lineFuncResult = GeoUtility.lineFunction(boatCurrentPos, gateMidPoint, markPoint2); // Integer lineFuncResult = GeoUtility.lineFunction(boatCurrentPos, gateMidPoint, markPoint2);
Line rightLayline = new Line(); // Line rightLayline = new Line();
Line leftLayline = new Line(); // Line leftLayline = new Line();
if (lineFuncResult == 1) { // if (lineFuncResult == 1) {
rightLayline = makeRightLayline(markPoint2, 180 - resultingAngle, StreamParser.getWindDirection()); // rightLayline = makeRightLayline(markPoint2, 180 - resultingAngle, StreamParser.getWindDirection());
leftLayline = makeLeftLayline(markPoint1, 180 - resultingAngle, StreamParser.getWindDirection()); // leftLayline = makeLeftLayline(markPoint1, 180 - resultingAngle, StreamParser.getWindDirection());
} else if (lineFuncResult == -1) { // } else if (lineFuncResult == -1) {
rightLayline = makeRightLayline(markPoint1, 180 - resultingAngle, StreamParser.getWindDirection()); // rightLayline = makeRightLayline(markPoint1, 180 - resultingAngle, StreamParser.getWindDirection());
leftLayline = makeLeftLayline(markPoint2, 180 - resultingAngle, StreamParser.getWindDirection()); // leftLayline = makeLeftLayline(markPoint2, 180 - resultingAngle, StreamParser.getWindDirection());
} // }
//
leftLayline.setStrokeWidth(0.5); // leftLayline.setStrokeWidth(0.5);
leftLayline.setStroke(bg.getBoat().getColour()); // leftLayline.setStroke(bg.getBoat().getColour());
//
rightLayline.setStrokeWidth(0.5); // rightLayline.setStrokeWidth(0.5);
rightLayline.setStroke(bg.getBoat().getColour()); // rightLayline.setStroke(bg.getBoat().getColour());
//
bg.setLaylines(leftLayline, rightLayline); // bg.setLaylines(leftLayline, rightLayline);
mg.addLaylines(leftLayline, rightLayline); // mg.addLaylines(leftLayline, rightLayline);
//
} // }
} // }
} // }
} // }
} // }
private Point2D getPointRotation(Point2D ref, Double distance, Double angle){ private Point2D getPointRotation(Point2D ref, Double distance, Double angle){
@@ -495,17 +502,27 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel
* for the combobox to take action upon selection * for the combobox to take action upon selection
*/ */
private void initialiseBoatSelectionComboBox() { private void initialiseBoatSelectionComboBox() {
updateBoatSelectionComboBox(); boatSelectionComboBox.setItems(
boatSelectionComboBox.valueProperty().addListener((observable, oldValue, newValue) -> { FXCollections.observableArrayList(participants.values())
//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) //Null check is if the listener is fired but nothing selected
if (newValue != null && newValue != selectedBoat) { boatSelectionComboBox.valueProperty().addListener((obs, lastSelection, selectedBoat) -> {
Boat thisBoat = (Boat) newValue; if (selectedBoat != null) {
setSelectedBoat(thisBoat); gameView.selectBoat(selectedBoat);
} }
}); });
} }
/**
* 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<Boat> observableBoats = FXCollections.observableArrayList();
observableBoats.addAll(participants.values());
boatSelectionComboBox.setItems(observableBoats);
}
/** /**
* Display the list of boats in the order they finished the race * Display the list of boats in the order they finished the race
@@ -525,38 +542,12 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel
} }
} }
private String getMillisToFormattedTime(long milliseconds) {
/** return String.format("%02d:%02d:%02d",
* Convert seconds to a string of the format mm:ss TimeUnit.MILLISECONDS.toHours(milliseconds),
* TimeUnit.MILLISECONDS.toMinutes(milliseconds) % 60, //Modulus 60 minutes per hour
* @param time the time in seconds TimeUnit.MILLISECONDS.toSeconds(milliseconds) % 60 //Modulus 60 seconds per minute
* @return a formatted string );
*/
public String convertTimeToMinutesSeconds(int time) {
if (time < 0) {
return String.format("-%02d:%02d", (time * -1) / 60, (time * -1) % 60);
}
return String.format("%02d:%02d", time / 60, time % 60);
}
private String getTimeSinceStartOfRace() {
String timerString = "0:00";
if (StreamParser.getTimeSinceStart() > 0) {
String timerMinute = Long.toString(StreamParser.getTimeSinceStart() / 60);
String timerSecond = Long.toString(StreamParser.getTimeSinceStart() % 60);
if (timerSecond.length() == 1) {
timerSecond = "0" + timerSecond;
}
timerString = "-" + timerMinute + ":" + timerSecond;
} else {
String timerMinute = Long.toString(-1 * StreamParser.getTimeSinceStart() / 60);
String timerSecond = Long.toString(-1 * StreamParser.getTimeSinceStart() % 60);
if (timerSecond.length() == 1) {
timerSecond = "0" + timerSecond;
}
timerString = timerMinute + ":" + timerSecond;
}
return timerString;
} }
private void setAnnotations(Integer annotationLevel) { private void setAnnotations(Integer annotationLevel) {
@@ -594,38 +585,21 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel
* @param boat The yacht for which we want to view all annotations * @param boat The yacht for which we want to view all annotations
*/ */
private void setSelectedBoat(Boat boat) { private void setSelectedBoat(Boat boat) {
for (BoatGroup bg : gameViewController.getBoatGroups()) { // for (BoatObject bg : gameViewController.getBoatGroups()) {
//We need to iterate over all race groups to get the matching boat group belonging to this boat if we // //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. // //are to toggle its annotations, there is no other backwards knowledge of a yacht to its boatgroup.
if (bg.getBoat().getHullID().equals(boat.getHullID())) { // if (bg.getBoat().getHullID().equals(boat.getHullID())) {
updateLaylines(bg); //// updateLaylines(bg);
bg.setIsSelected(true); // bg.setIsSelected(true);
selectedBoat = boat; //// selectedBoat = boat;
} else { // } else {
bg.setIsSelected(false); // bg.setIsSelected(false);
} // }
} // }
}
void setStage(Stage stage) {
this.stage = stage;
}
Stage getStage() {
return stage;
}
/**
* Used for when the boat attempts to add data to the sparkline (first checks if the sparkline contains info on it)
* @param yachtId
* @return
*/
public static boolean sparkLineStatus(Integer yachtId) {
return sparkLineData.containsKey(yachtId);
} }
public void updateRaceData (RaceXMLData raceData) { public void updateRaceData (RaceXMLData raceData) {
this.raceData = raceData; this.courseData = raceData;
gameView.updateBorder(raceData.getCourseLimit()); gameView.updateBorder(raceData.getCourseLimit());
} }
@@ -61,7 +61,6 @@ public class StartScreenController {
ClientToServerThread clientToServerThread = new ClientToServerThread("localhost", 4950); ClientToServerThread clientToServerThread = new ClientToServerThread("localhost", 4950);
// controller.setClientToServerThread(clientToServerThread); // controller.setClientToServerThread(clientToServerThread);
clientToServerThread.start(); clientToServerThread.start();
// new GameServerThread("Fuck you");
// get the lobby controller so that we can pass the game server thread to it // get the lobby controller so that we can pass the game server thread to it
setContentPane("/views/LobbyView.fxml"); setContentPane("/views/LobbyView.fxml");
@@ -1,20 +1,18 @@
package seng302.visualiser.controllers.client; package seng302.visualiser.controllers.client;
import java.text.DateFormat; import java.io.IOException;
import java.text.SimpleDateFormat;
import java.time.ZoneId; import java.time.ZoneId;
import java.time.ZoneOffset; import java.time.ZoneOffset;
import java.util.Date;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.TimeZone; import java.util.TimeZone;
import javafx.fxml.FXMLLoader; import javafx.fxml.FXMLLoader;
import javafx.scene.layout.Pane; import javafx.scene.layout.Pane;
import seng302.model.Boat; import seng302.model.Boat;
import seng302.model.RaceState;
import seng302.model.mark.Mark; import seng302.model.mark.Mark;
import seng302.model.stream.parsers.PositionUpdateData.DeviceType; import seng302.model.stream.parsers.PositionUpdateData.DeviceType;
import seng302.model.stream.parsers.MarkRoundingData; import seng302.model.stream.parsers.MarkRoundingData;
import seng302.model.stream.parsers.RaceStartData;
import seng302.model.stream.parsers.RaceStatusData; import seng302.model.stream.parsers.RaceStatusData;
import seng302.model.stream.parsers.xml.RaceXMLData; import seng302.model.stream.parsers.xml.RaceXMLData;
import seng302.model.stream.parsers.StreamParser; import seng302.model.stream.parsers.StreamParser;
@@ -22,7 +20,6 @@ import seng302.model.stream.parsers.xml.RegattaXMLData;
import seng302.model.stream.parsers.xml.XMLParser; import seng302.model.stream.parsers.xml.XMLParser;
import seng302.model.stream.parsers.PositionUpdateData; import seng302.model.stream.parsers.PositionUpdateData;
import seng302.model.stream.packets.StreamPacket; import seng302.model.stream.packets.StreamPacket;
import seng302.visualiser.ClientSocketListener;
import seng302.visualiser.ClientToServerThread; import seng302.visualiser.ClientToServerThread;
import seng302.visualiser.controllers.RaceViewController; import seng302.visualiser.controllers.RaceViewController;
@@ -31,35 +28,37 @@ import seng302.visualiser.controllers.RaceViewController;
*/ */
public class ClientController { public class ClientController {
private final DateFormat DATE_TIME_FORMAT = new SimpleDateFormat("dd/MM/yyyy HH:mm:ss");
private Pane holderPane; private Pane holderPane;
private ClientToServerThread socketThread; private ClientToServerThread socketThread;
private ClientSocketListener socketListener;
private RaceViewController raceView; private RaceViewController raceView;
private Map<Integer, Boat> allBoatsMap; private Map<Integer, Boat> allBoatsMap;
private Map<Integer, Boat> racingBoats = new HashMap<>(); private Map<Integer, Boat> racingBoats = new HashMap<>();
private RegattaXMLData regattaData; private RegattaXMLData regattaData;
private RaceXMLData raceData; private RaceXMLData courseData;
private RaceState raceState = new RaceState();
public ClientController (String ipAddress, Pane holder) { public ClientController (String ipAddress, Pane holder) {
this.holderPane = holder; this.holderPane = holder;
socketThread = new ClientToServerThread(ipAddress, 4950); socketThread = new ClientToServerThread(ipAddress, 4950);
socketThread.start(); socketThread.start();
socketListener = this::parsePacket; socketThread.addStreamObserver(this::parsePacket);
socketThread.addStreamObserver(socketListener);
} }
private void loadRaceView () { private void loadRaceView () {
allBoatsMap.forEach((id, boat) -> { allBoatsMap.forEach((id, boat) -> {
if (raceData.getParticipants().contains(id)) if (courseData.getParticipants().contains(id))
racingBoats.put(id, boat); racingBoats.put(id, boat);
}); });
FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("RaceView.fxml")); FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("RaceView.fxml"));
raceView = fxmlLoader.getController(); raceView = fxmlLoader.getController();
raceView.loadRace(racingBoats, raceData); try {
holderPane.getChildren().add(fxmlLoader.load());
} catch (IOException e) {
e.printStackTrace();
}
raceView.loadRace(racingBoats, courseData, raceState);
} }
private void parsePacket(StreamPacket packet) { private void parsePacket(StreamPacket packet) {
@@ -72,49 +71,47 @@ public class ClientController {
regattaData = XMLParser.parseRegatta( regattaData = XMLParser.parseRegatta(
StreamParser.extractXmlMessage(packet) StreamParser.extractXmlMessage(packet)
); );
DATE_TIME_FORMAT.setTimeZone( raceState.setTimeZone(
TimeZone.getTimeZone( TimeZone.getTimeZone(
ZoneId.ofOffset("UTC", ZoneOffset.ofHours(regattaData.getUtcOffset())) ZoneId.ofOffset("UTC", ZoneOffset.ofHours(regattaData.getUtcOffset()))
) )
); );
startRaceIfAllDataRecieved(); startRaceIfAllDataReceived();
break; break;
case RACE_XML: case RACE_XML:
raceData = XMLParser.parseRace( courseData = XMLParser.parseRace(
StreamParser.extractXmlMessage(packet) StreamParser.extractXmlMessage(packet)
); );
if (raceView != null) { if (raceView != null) {
raceView.updateRaceData(raceData); raceView.updateRaceData(courseData);
} }
startRaceIfAllDataRecieved(); startRaceIfAllDataReceived();
break; break;
case BOAT_XML: case BOAT_XML:
allBoatsMap = XMLParser.parseBoats( allBoatsMap = XMLParser.parseBoats(
StreamParser.extractXmlMessage(packet) StreamParser.extractXmlMessage(packet)
); );
startRaceIfAllDataRecieved(); startRaceIfAllDataReceived();
break; break;
case RACE_START_STATUS: case RACE_START_STATUS:
RaceStartData raceStartData = StreamParser.extractRaceStartStatus(packet); raceState.updateState(StreamParser.extractRaceStartStatus(packet));
break; break;
case BOAT_LOCATION: case BOAT_LOCATION:
PositionUpdateData positionData = StreamParser.extractBoatLocation(packet); updatePosition(StreamParser.extractBoatLocation(packet));
updatePosition(positionData);
break; break;
case MARK_ROUNDING: case MARK_ROUNDING:
MarkRoundingData roundingData = StreamParser.extractMarkRounding(packet); updateMarkRounding(StreamParser.extractMarkRounding(packet));
updateMarkRounding(roundingData);
break; break;
} }
} }
private void startRaceIfAllDataRecieved () { private void startRaceIfAllDataReceived() {
if (raceData != null && allBoatsMap != null && regattaData != null) if (courseData != null && allBoatsMap != null && regattaData != null)
loadRaceView(); loadRaceView();
} }
@@ -129,8 +126,8 @@ public class ClientController {
boat.setLat(positionData.getLat()); boat.setLat(positionData.getLat());
boat.setLon(positionData.getLon()); boat.setLon(positionData.getLon());
boat.setHeading(positionData.getHeading()); boat.setHeading(positionData.getHeading());
} else { } else if (positionData.getType() == DeviceType.MARK_TYPE) {
Mark mark = raceData.getCompoundMarks().get(positionData.getDeviceId()); Mark mark = courseData.getCompoundMarks().get(positionData.getDeviceId());
} }
} }
@@ -142,21 +139,19 @@ public class ClientController {
private void updateMarkRounding(MarkRoundingData roundingData) { private void updateMarkRounding(MarkRoundingData roundingData) {
Boat boat = racingBoats.get(roundingData.getBoatId()); Boat boat = racingBoats.get(roundingData.getBoatId());
boat.setMarkRoundingTime(roundingData.getTimeStamp()); boat.setMarkRoundingTime(roundingData.getTimeStamp());
boat.setTimeSinceLastMark(raceState.getRaceTime() - roundingData.getTimeStamp());
boat.setLastMarkRounded( boat.setLastMarkRounded(
raceData.getCompoundMarks().get( courseData.getCompoundMarks().get(
roundingData.getMarkId() roundingData.getMarkId()
) )
); );
} }
private void processRaceStatusUpdate (RaceStatusData data) { private void processRaceStatusUpdate (RaceStatusData data) {
String raceTimeStr = DATE_TIME_FORMAT.format(data.getCurrentTime()); raceState.updateState(data);
Date date = new Date();
date.getTime();
long timeTillStart = (data.getExpectedStartTime() - data.getCurrentTime()) / 1000;
for (long[] boatData : data.getBoatData()) { for (long[] boatData : data.getBoatData()) {
Boat boat = allBoatsMap.get((int) boatData[0]); Boat boat = allBoatsMap.get((int) boatData[0]);
boat.setEstimateTimeAtNextMark(boatData[1]); boat.setEstimateTimeTillNextMark(raceState.getRaceTime() - boatData[1]);
boat.setEstimateTimeAtFinish(boatData[2]); boat.setEstimateTimeAtFinish(boatData[2]);
int legNumber = (int) boatData[3]; int legNumber = (int) boatData[3];
boat.setLegNumber(legNumber); boat.setLegNumber(legNumber);
@@ -172,6 +167,10 @@ public class ClientController {
} }
} }
private void close () {
socketThread.closeSocket();
}
// /** Handle the key-pressed event from the text field. */ // /** Handle the key-pressed event from the text field. */
// public void keyPressed(KeyEvent e) { // public void keyPressed(KeyEvent e) {
// BoatActionMessage boatActionMessage; // BoatActionMessage boatActionMessage;
@@ -201,7 +200,7 @@ public class ClientController {
// break; // break;
// } // }
// } // }
//
// public void keyReleased(KeyEvent e) { // public void keyReleased(KeyEvent e) {
// switch (e.getCode()) { // switch (e.getCode()) {
// //TODO 12/07/17 Determine the sail state and send the appropriate packet (eg. if sails are in, send a sail out packet) // //TODO 12/07/17 Determine the sail state and send the appropriate packet (eg. if sails are in, send a sail out packet)
@@ -0,0 +1,185 @@
package seng302.visualiser.fxObjects;
import java.util.HashMap;
import java.util.Map;
import javafx.beans.value.ObservableValue;
import javafx.scene.CacheHint;
import javafx.scene.Group;
import javafx.scene.paint.Color;
import javafx.scene.paint.Paint;
import javafx.scene.shape.Rectangle;
import javafx.scene.text.Text;
/**
* Grouping of string objects over a semi transparent background.
*/
public class AnnotationBox extends Group{
@FunctionalInterface
public interface AnnotationFormatter<T> {
String transformString (T input);
}
/**
* Class stores a text object and relationship for updating the text object if needed
*
* @param <T> The type of observable value passed to the annotation, if there is one.
*/
public class Annotation<T> {
private Text text;
private ObservableValue<T> source;
private AnnotationFormatter<T> format;
/**
* Constructor for observing annotation
* @param textObject the javaFX text object the annotation is displayed in
* @param source observable value that the annotation is taken from
* @param formatter interface describing how to format the source data if needed
*/
public Annotation (Text textObject, ObservableValue<T> source, AnnotationFormatter<T> formatter) {
this.text = textObject;
this.source = source;
this.format = formatter;
source.addListener((obs, oldVal, newVal) ->
text.setText(format.transformString(newVal))
);
}
/**
* Constructor for a static annotation
* @param textObject the javaFX text object the annotation is displayed in
* @param annotationText the static value of the test object
*/
public Annotation (Text textObject, String annotationText) {
textObject.setText(annotationText);
text = textObject;
}
private Text getText () {
return text;
}
}
//Text offset constants
private static final double X_OFFSET_TEXT = 18d;
private static final double Y_OFFSET_TEXT_INIT = -29d;
private static final double Y_OFFSET_PER_TEXT = 12d;
//Background constants
private static final double TEXT_BUFFER = 3;
private static final double BACKGROUND_X = X_OFFSET_TEXT - TEXT_BUFFER;
private static final double BACKGROUND_Y = Y_OFFSET_TEXT_INIT - TEXT_BUFFER;
private static final double BACKGROUND_H_PER_TEXT = 9.5d;
private static final double BACKGROUND_ARC_SIZE = 10;
private int visibleAnnotations = 0;
private double backgroundWidth = 125d;
private Rectangle background = new Rectangle();
private Paint theme = Color.BLACK;
private Map<String, Annotation> annotationsByName = new HashMap<>();
public AnnotationBox() {
this.setCache(true);
background.setX(BACKGROUND_X);
background.setY(BACKGROUND_Y);
background.setWidth(backgroundWidth);
background.setHeight(Math.abs(BACKGROUND_X) + TEXT_BUFFER + BACKGROUND_H_PER_TEXT * 4);
background.setArcHeight(BACKGROUND_ARC_SIZE);
background.setArcWidth(BACKGROUND_ARC_SIZE);
background.setFill(new Color(1, 1, 1, 0.75));
background.setStroke(theme);
background.setStrokeWidth(2);
background.setCache(true);
background.setCacheHint(CacheHint.SPEED);
this.getChildren().add(background);
}
public void addAnnotation (String annotationName, Annotation annotation) {
annotationsByName.put(annotationName, annotation);
this.getChildren().add(annotation.getText());
visibleAnnotations++;
}
public void addAnnotation (String annotationName, String annotationText) {
Text text = getTextObject();
annotationsByName.put(annotationName, new Annotation(text, annotationText));
}
public <E> void addAnnotation (String annotationName, ObservableValue<E> observable) {
addAnnotation(annotationName, observable, E::toString);
}
public <E> void addAnnotation (String annotationName, ObservableValue<E> observable,
AnnotationFormatter<E> formatter) {
Text newText = getTextObject();
annotationsByName.put(annotationName, new Annotation<>(newText, observable, formatter));
this.getChildren().add(newText);
visibleAnnotations++;
}
public void setAnnotationVisibility (String annotationName, boolean visibility) {
Text textField = annotationsByName.get(annotationName).text;
boolean currentState = textField.visibleProperty().get();
if (visibility != currentState) {
if (visibility)
visibleAnnotations++;
else
visibleAnnotations--;
}
textField.setVisible(visibility);
update();
}
public void removeAnnotation (String annotationName) {
this.getChildren().remove(annotationsByName.remove(annotationName).getText());
annotationsByName.remove(annotationName);
visibleAnnotations--;
update();
}
public void bindLocation () {
}
public void setLocation (double x, double y) {
this.setLayoutX(x);
this.setLayoutY(y);
}
public void setWidth (double width) {
backgroundWidth = width;
background.setWidth(backgroundWidth);
}
private void update () {
background.setVisible(visibleAnnotations != 0);
background.setHeight(Math.abs(BACKGROUND_X) + TEXT_BUFFER + BACKGROUND_H_PER_TEXT * visibleAnnotations);
for (int i = 1; i <= visibleAnnotations; i++) {
Text text = (Text) this.getChildren().get(i);
if (text.visibleProperty().get())
text.relocate(X_OFFSET_TEXT, Y_OFFSET_TEXT_INIT * Y_OFFSET_PER_TEXT * (i + 1));
}
}
/**
* Return a text object with caching and a color applied
*
* @return The text object
*/
private Text getTextObject() {
Text text = new Text();
text.setFill(theme);
text.setStrokeWidth(2);
text.setCacheHint(CacheHint.SPEED);
text.setCache(true);
return text;
}
public void setFill (Paint value) {
theme = value;
background.setStroke(theme);
annotationsByName.forEach((name, annotation) -> annotation.getText().setFill(theme));
}
}
@@ -1,132 +0,0 @@
package seng302.visualiser.fxObjects;
import javafx.scene.CacheHint;
import javafx.scene.Group;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.scene.text.Text;
import seng302.model.Boat;
import seng302.model.stream.parsers.StreamParser;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
/**
* Collection of annotations for boats.
*/
public class BoatAnnotations extends Group{
//Text offset constants
private static final double X_OFFSET_TEXT = 18d;
private static final double Y_OFFSET_TEXT_INIT = -29d;
private static final double Y_OFFSET_PER_TEXT = 12d;
//Background constants
private static final double TEXT_BUFFER = 3;
private static final double BACKGROUND_X = X_OFFSET_TEXT - TEXT_BUFFER;
private static final double BACKGROUND_Y = Y_OFFSET_TEXT_INIT - TEXT_BUFFER;
private static final double BACKGROUND_H_PER_TEXT = 9.5d;
private static final double BACKGROUND_W = 125d;
private static final double BACKGROUND_ARC_SIZE = 10;
private Rectangle background = new Rectangle();
private Text teamNameObject;
private Text velocityObject;
private Text estTimeToNextMarkObject;
private Text legTimeObject;
private Boat boat;
BoatAnnotations (Boat boat, Color theme) {
super.setCache(true);
this.boat = boat;
background.setX(BACKGROUND_X);
background.setY(BACKGROUND_Y);
background.setWidth(BACKGROUND_W);
background.setHeight(Math.abs(BACKGROUND_X) + TEXT_BUFFER + BACKGROUND_H_PER_TEXT * 4);
background.setArcHeight(BACKGROUND_ARC_SIZE);
background.setArcWidth(BACKGROUND_ARC_SIZE);
background.setFill(new Color(1, 1, 1, 0.75));
background.setStroke(theme);
background.setStrokeWidth(2);
background.setCache(true);
background.setCacheHint(CacheHint.SPEED);
teamNameObject = getTextObject(boat.getShortName(), theme);
teamNameObject.relocate(X_OFFSET_TEXT, Y_OFFSET_TEXT_INIT + Y_OFFSET_PER_TEXT);
velocityObject = getTextObject("0 m/s", theme);
velocityObject.relocate(X_OFFSET_TEXT, Y_OFFSET_TEXT_INIT + Y_OFFSET_PER_TEXT * 2);
estTimeToNextMarkObject = getTextObject("Next mark: ", theme);
estTimeToNextMarkObject.relocate(X_OFFSET_TEXT, Y_OFFSET_TEXT_INIT + Y_OFFSET_PER_TEXT * 3);
legTimeObject = getTextObject("Last mark: -", theme);
legTimeObject.relocate(X_OFFSET_TEXT, Y_OFFSET_TEXT_INIT + Y_OFFSET_PER_TEXT * 4);
super.getChildren().addAll(background, teamNameObject, velocityObject, estTimeToNextMarkObject, legTimeObject);
}
/**
* Return a text object with caching and a color applied
*
* @param defaultText The default text to display
* @param fill The text fill color
* @return The text object
*/
private Text getTextObject(String defaultText, Color fill) {
Text text = new Text(defaultText);
text.setFill(fill);
text.setStrokeWidth(2);
text.setCacheHint(CacheHint.SPEED);
text.setCache(true);
return text;
}
void update () {
velocityObject.setText(String.format("%.2f m/s", boat.getVelocity()));
DateFormat format = new SimpleDateFormat("mm:ss");
if (boat.getTimeTillNext() != null) {
String timeToNextMark = format
.format(boat.getTimeTillNext() - StreamParser.getCurrentTimeLong());
estTimeToNextMarkObject.setText("Next mark: " + timeToNextMark);
} else {
estTimeToNextMarkObject.setText("Next mark: -");
}
if (boat.getMarkRoundTime() != null) {
String elapsedTime = format
.format(StreamParser.getCurrentTimeLong() - boat.getMarkRoundTime());
legTimeObject.setText("Last mark: " + elapsedTime);
}else {
legTimeObject.setText("Last mark: - ");
}
}
void setVisibile (boolean nameVisibility, boolean speedVisibility,
boolean estTimeVisibility, boolean lastMarkVisibility) {
int totalVisible = 0;
totalVisible = updateVisibility(nameVisibility, teamNameObject, totalVisible);
totalVisible = updateVisibility(speedVisibility, velocityObject, totalVisible);
totalVisible = updateVisibility(estTimeVisibility, estTimeToNextMarkObject, totalVisible);
totalVisible = updateVisibility(lastMarkVisibility, legTimeObject, totalVisible);
if (totalVisible != 0) {
background.setVisible(true);
background.setHeight(Math.abs(BACKGROUND_X) + TEXT_BUFFER + BACKGROUND_H_PER_TEXT * totalVisible);
} else {
background.setVisible(false);
}
}
private int updateVisibility (boolean visibility, Text text, int totalVisible) {
if (visibility){
totalVisible ++;
text.setVisible(true);
text.setLayoutX(X_OFFSET_TEXT);
text.setLayoutY(Y_OFFSET_TEXT_INIT + Y_OFFSET_PER_TEXT * totalVisible);
} else {
text.setVisible(false);
}
return totalVisible;
}
}
@@ -6,10 +6,10 @@ import javafx.geometry.Point2D;
import javafx.scene.CacheHint; 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.paint.Paint;
import javafx.scene.shape.Line; import javafx.scene.shape.Line;
import javafx.scene.shape.Polygon; import javafx.scene.shape.Polygon;
import javafx.scene.transform.Rotate; import javafx.scene.transform.Rotate;
import seng302.visualiser.controllers.GameViewController;
import seng302.model.Boat; import seng302.model.Boat;
import seng302.utilities.GeoUtility; import seng302.utilities.GeoUtility;
import seng302.model.mark.GateMark; import seng302.model.mark.GateMark;
@@ -25,7 +25,7 @@ import seng302.model.stream.parsers.StreamParser;
* minimized in which case it attempts to store animations and apply them when the window is * minimized in which case it attempts to store animations and apply them when the window is
* maximised. * maximised.
*/ */
public class BoatGroup extends Group { public class BoatObject extends Group {
//Constants for drawing //Constants for drawing
private static final double BOAT_HEIGHT = 15d; private static final double BOAT_HEIGHT = 15d;
@@ -47,79 +47,57 @@ public class BoatGroup extends Group {
private Double distanceTravelled = 0.0; private Double distanceTravelled = 0.0;
private Point2D lastPoint; private Point2D lastPoint;
private boolean destinationSet; private boolean destinationSet;
private BoatAnnotations boatAnnotations; private AnnotationBox annotationBox;
private Paint colour = Color.BLACK;
private Boolean isSelected = true; //All boats are initialised as selected private Boolean isSelected = true; //All boats are initialised as selected
/** /**
* Creates a BoatGroup with the default triangular boat polygon. * Creates a BoatGroup with the default triangular boat polygon.
*
* @param boat The boat that the BoatGroup will represent. Must contain an ID which will be used
* to tell which BoatGroup to update.
* @param color The colour of the boat polygon and the trailing line.
*/ */
public BoatGroup(Boat boat, Color color) { public BoatObject() {
destinationSet = false; this(-BOAT_WIDTH / 2, BOAT_HEIGHT / 2,
this.boat = boat; 0.0, -BOAT_HEIGHT / 2,
initChildren(color); BOAT_WIDTH / 2, BOAT_HEIGHT / 2);
} }
/** /**
* Creates a BoatGroup with the boat being the default polygon. The head of the boat should be * Creates a BoatGroup with the boat being the default polygon. The head of the boat should be
* at point (0,0). * at point (0,0).
* *
* @param boat The boat that the BoatGroup will represent. Must contain an ID which will be used
* to tell which BoatGroup to update.
* @param color The colour of the boat polygon and the trailing line.
* @param points An array of co-ordinates x1,y1,x2,y2,x3,y3... that will make up the boat * @param points An array of co-ordinates x1,y1,x2,y2,x3,y3... that will make up the boat
* polygon. * polygon.
*/ */
public BoatGroup(Boat boat, Color color, double... points) { public BoatObject(double... points) {
destinationSet = false;
this.boat = boat;
initChildren(color, points);
}
/**
* Creates the javafx objects that will be the in the group by default.
*
* @param color The colour of the boat polygon and the trailing line.
* @param points An array of co-ordinates x1,y1,x2,y2,x3,y3... that will make up the boat
* polygon.
*/
private void initChildren(Color color, double... points) {
boatPoly = new Polygon(points); boatPoly = new Polygon(points);
boatPoly.setFill(color); boatPoly.setFill(colour);
boatPoly.setOnMouseEntered(event -> { boatPoly.setOnMouseEntered(event -> {
boatPoly.setFill(Color.FLORALWHITE); boatPoly.setFill(Color.FLORALWHITE);
boatPoly.setStroke(Color.RED); boatPoly.setStroke(Color.RED);
}); });
boatPoly.setOnMouseExited(event -> { boatPoly.setOnMouseExited(event -> {
boatPoly.setFill(color); boatPoly.setFill(colour);
boatPoly.setStroke(Color.BLACK); boatPoly.setStroke(Color.BLACK);
}); });
boatPoly.setOnMouseClicked(event -> setIsSelected(!isSelected)); boatPoly.setOnMouseClicked(event -> setIsSelected(!isSelected));
boatPoly.setCache(true); boatPoly.setCache(true);
boatPoly.setCacheHint(CacheHint.SPEED); boatPoly.setCacheHint(CacheHint.SPEED);
boatAnnotations = new BoatAnnotations(boat, color);
annotationBox = new AnnotationBox();
annotationBox.setFill(colour);
leftLayLine = new Line(); leftLayLine = new Line();
rightLayline = new Line(); rightLayline = new Line();
wake = new Wake(0, -BOAT_HEIGHT); wake = new Wake(0, -BOAT_HEIGHT);
super.getChildren().addAll(boatPoly, boatAnnotations); super.getChildren().addAll(boatPoly, annotationBox);
} }
/** public void setFill (Paint value) {
* Creates the javafx objects that will be the in the group by default. this.colour = value;
* boatPoly.setFill(colour);
* @param color The colour of the boat polygon and the trailing line. annotationBox.setFill(colour);
*/
private void initChildren(Color color) {
initChildren(color,
-BOAT_WIDTH / 2, BOAT_HEIGHT / 2,
0.0, -BOAT_HEIGHT / 2,
BOAT_WIDTH / 2, BOAT_HEIGHT / 2);
} }
/** /**
@@ -132,8 +110,8 @@ public class BoatGroup extends Group {
private void moveGroupBy(double dx, double dy) { private void moveGroupBy(double dx, double dy) {
boatPoly.setLayoutX(boatPoly.getLayoutX() + dx); boatPoly.setLayoutX(boatPoly.getLayoutX() + dx);
boatPoly.setLayoutY(boatPoly.getLayoutY() + dy); boatPoly.setLayoutY(boatPoly.getLayoutY() + dy);
boatAnnotations.setLayoutX(boatAnnotations.getLayoutX() + dx); annotationBox.setLayoutX(annotationBox.getLayoutX() + dx);
boatAnnotations.setLayoutY(boatAnnotations.getLayoutY() + dy); annotationBox.setLayoutY(annotationBox.getLayoutY() + dy);
wake.setLayoutX(wake.getLayoutX() + dx); wake.setLayoutX(wake.getLayoutX() + dx);
wake.setLayoutY(wake.getLayoutY() + dy); wake.setLayoutY(wake.getLayoutY() + dy);
} }
@@ -149,8 +127,8 @@ public class BoatGroup extends Group {
rotateTo(rotation); rotateTo(rotation);
boatPoly.setLayoutX(x); boatPoly.setLayoutX(x);
boatPoly.setLayoutY(y); boatPoly.setLayoutY(y);
boatAnnotations.setLayoutX(x); annotationBox.setLayoutX(x);
boatAnnotations.setLayoutY(y); annotationBox.setLayoutY(y);
wake.setLayoutX(x); wake.setLayoutX(x);
wake.setLayoutY(y); wake.setLayoutY(y);
wake.rotate(rotation); wake.rotate(rotation);
@@ -225,54 +203,52 @@ public class BoatGroup extends Group {
lastTimeValid = timeValid; lastTimeValid = timeValid;
isStopped = false; isStopped = false;
lastRotation = rotation; lastRotation = rotation;
boatAnnotations.update();
} }
/** // /**
* This function works out if a boat is going upwind or down wind. It looks at the boats current position, the next // * This function works out if a boat is going upwind or down wind. It looks at the boats current position, the next
* gates position and the current wind // * gates position and the current wind
* If bot the wind vector from the next gate and the boat from the next gate lay on the same side, then the boat is // * If bot the wind vector from the next gate and the boat from the next gate lay on the same side, then the boat is
* going up wind, if they are on different sides of the gate, then the boat is going downwind // * going up wind, if they are on different sides of the gate, then the boat is going downwind
* @param canvasController // * @param canvasController
*/ // */
public Boolean isUpwindLeg(GameViewController canvasController, Mark nextMark) { // public Boolean isUpwindLeg(GameViewController canvasController, Mark nextMark) {
//
Double windAngle = StreamParser.getWindDirection(); // Double windAngle = StreamParser.getWindDirection();
GateMark thisGateMark = (GateMark) nextMark; // GateMark thisGateMark = (GateMark) nextMark;
SingleMark nextMark1 = thisGateMark.getSingleMark1(); // SingleMark nextMark1 = thisGateMark.getSingleMark1();
SingleMark nextMark2 = thisGateMark.getSingleMark2(); // SingleMark nextMark2 = thisGateMark.getSingleMark2();
Point2D nextMarkPoint1 = canvasController.findScaledXY(nextMark1.getLatitude(), nextMark1.getLongitude()); // Point2D nextMarkPoint1 = canvasController.findScaledXY(nextMark1.getLatitude(), nextMark1.getLongitude());
Point2D nextMarkPoint2 = canvasController.findScaledXY(nextMark2.getLatitude(), nextMark2.getLongitude()); // Point2D nextMarkPoint2 = canvasController.findScaledXY(nextMark2.getLatitude(), nextMark2.getLongitude());
//
Point2D boatCurrentPoint = new Point2D(boatPoly.getLayoutX(), boatPoly.getLayoutY()); // Point2D boatCurrentPoint = new Point2D(boatPoly.getLayoutX(), boatPoly.getLayoutY());
Point2D windTestPoint = GeoUtility.makeArbitraryVectorPoint(nextMarkPoint1, windAngle, 10d); // Point2D windTestPoint = GeoUtility.makeArbitraryVectorPoint(nextMarkPoint1, windAngle, 10d);
//
//
Integer boatLineFuncResult = GeoUtility.lineFunction(nextMarkPoint1, nextMarkPoint2, boatCurrentPoint); // Integer boatLineFuncResult = GeoUtility.lineFunction(nextMarkPoint1, nextMarkPoint2, boatCurrentPoint);
Integer windLineFuncResult = GeoUtility.lineFunction(nextMarkPoint1, nextMarkPoint2, windTestPoint); // Integer windLineFuncResult = GeoUtility.lineFunction(nextMarkPoint1, nextMarkPoint2, windTestPoint);
//
//
/* // /*
If both the wind vector from the gate and the boat from the gate are on the same side of that gate, then the // If both the wind vector from the gate and the boat from the gate are on the same side of that gate, then the
boat is travelling into the wind. thus upwind. Otherwise if they are on different sides, then the boat is going // boat is travelling into the wind. thus upwind. Otherwise if they are on different sides, then the boat is going
with the wind. // with the wind.
*/ // */
return boatLineFuncResult.equals(windLineFuncResult); // return boatLineFuncResult.equals(windLineFuncResult);
} // return true;
// }
public void setIsSelected(Boolean isSelected) { public void setIsSelected(Boolean isSelected) {
this.isSelected = isSelected; this.isSelected = isSelected;
setLineGroupVisible(isSelected); setLineGroupVisible(isSelected);
setWakeVisible(isSelected); setWakeVisible(isSelected);
boatAnnotations.setVisible(isSelected); annotationBox.setVisible(isSelected);
setLayLinesVisible(isSelected); setLayLinesVisible(isSelected);
} }
public void setVisibility (boolean teamName, boolean velocity, boolean estTime, boolean legTime, public void setVisibility (boolean teamName, boolean velocity, boolean estTime, boolean legTime,
boolean trail, boolean wake) { boolean trail, boolean wake) {
boatAnnotations.setVisibile(teamName, velocity, estTime, legTime);
this.wake.setVisible(wake); this.wake.setVisible(wake);
this.lineGroup.setVisible(trail); this.lineGroup.setVisible(trail);
} }
@@ -324,7 +300,7 @@ public class BoatGroup extends Group {
} }
public Group getAnnotations() { public Group getAnnotations() {
return boatAnnotations; return annotationBox;
} }
public Double getBoatLayoutX() { public Double getBoatLayoutX() {
@@ -16,7 +16,7 @@ import seng302.model.mark.SingleMark;
/** /**
* Grouping of javaFX objects needed to represent a Mark on screen. * Grouping of javaFX objects needed to represent a Mark on screen.
*/ */
public class MarkGroup extends Group { public class MarkObject extends Group {
private static int MARK_RADIUS = 5; private static int MARK_RADIUS = 5;
private static int LINE_THICKNESS = 2; private static int LINE_THICKNESS = 2;
@@ -31,7 +31,7 @@ public class MarkGroup extends Group {
* @param mark * @param mark
* @param points * @param points
*/ */
public MarkGroup (SingleMark mark, Point2D points) { public MarkObject(SingleMark mark, Point2D points) {
marks.add(mark); marks.add(mark);
mainMark = mark; mainMark = mark;
Color color = Color.BLACK; Color color = Color.BLACK;
@@ -73,7 +73,7 @@ public class MarkGroup extends Group {
super.getChildren().removeAll(toRemove); super.getChildren().removeAll(toRemove);
} }
public MarkGroup(GateMark mark, Point2D points1, Point2D points2) { public MarkObject(GateMark mark, Point2D points1, Point2D points2) {
marks.add(mark.getSingleMark1()); marks.add(mark.getSingleMark1());
marks.add(mark.getSingleMark2()); marks.add(mark.getSingleMark2());
mainMark = mark; mainMark = mark;