mirror of
https://github.com/michaelrausch/Party-Parrots-At-Sea.git
synced 2026-05-09 14:28:43 +00:00
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:
@@ -1,9 +1,11 @@
|
||||
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 seng302.model.mark.Mark;
|
||||
import seng302.model.stream.packets.StreamPacket;
|
||||
import seng302.visualiser.controllers.RaceViewController;
|
||||
|
||||
import java.text.DateFormat;
|
||||
import java.text.SimpleDateFormat;
|
||||
@@ -30,15 +32,14 @@ public class Boat {
|
||||
private Integer boatStatus;
|
||||
private Integer legNumber = 0;
|
||||
private Integer position = 0;
|
||||
private Integer penaltiesAwarded;
|
||||
private Integer penaltiesServed;
|
||||
private Long estimateTimeAtFinish;
|
||||
private Long markRoundTime;
|
||||
private Double lat;
|
||||
private Double lon;
|
||||
private Double heading;
|
||||
private double velocity;
|
||||
private Long timeTillNext;
|
||||
private Long markRoundTime;
|
||||
private ReadOnlyDoubleWrapper velocity = new ReadOnlyDoubleWrapper();
|
||||
private ReadOnlyLongWrapper timeTillNext = new ReadOnlyLongWrapper();
|
||||
private ReadOnlyLongWrapper timeSinceLastMark = new ReadOnlyLongWrapper();
|
||||
|
||||
// Mark rounding
|
||||
private Mark lastMarkRounded;
|
||||
@@ -94,8 +95,8 @@ public class Boat {
|
||||
this.legNumber = legNumber;
|
||||
}
|
||||
|
||||
public void setEstimateTimeAtNextMark(Long estimateTimeAtNextMark) {
|
||||
timeTillNext = estimateTimeAtNextMark;
|
||||
public void setEstimateTimeTillNextMark(Long estimateTimeAtNextMark) {
|
||||
timeTillNext.set(estimateTimeAtNextMark);
|
||||
}
|
||||
|
||||
public String getEstimateTimeAtFinish() {
|
||||
@@ -124,7 +125,7 @@ public class Boat {
|
||||
}
|
||||
|
||||
public void setVelocity(double velocity) {
|
||||
this.velocity = velocity;
|
||||
this.velocity.set(velocity);
|
||||
}
|
||||
|
||||
|
||||
@@ -132,12 +133,12 @@ public class Boat {
|
||||
this.markRoundTime = markRoundingTime;
|
||||
}
|
||||
|
||||
public double getVelocity() {
|
||||
return velocity;
|
||||
public ReadOnlyDoubleProperty getVelocityProperty() {
|
||||
return velocity.getReadOnlyProperty();
|
||||
}
|
||||
|
||||
public Long getTimeTillNext() {
|
||||
return timeTillNext;
|
||||
public ReadOnlyLongProperty timeTillNextProperty() {
|
||||
return timeTillNext.getReadOnlyProperty();
|
||||
}
|
||||
|
||||
public Long getMarkRoundTime() {
|
||||
@@ -189,4 +190,12 @@ public class Boat {
|
||||
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;
|
||||
}
|
||||
|
||||
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
|
||||
* 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)]);
|
||||
Long estTimeAtNextMark = bytesToLong(
|
||||
Arrays.copyOfRange(payload, 32 + (i * 20), 38 + (i * 20)));
|
||||
// boat.setEstimateTimeAtNextMark(estTimeAtNextMark);
|
||||
// boat.setEstimateTimeTillNextMark(estTimeAtNextMark);
|
||||
Long estTimeAtFinish = bytesToLong(
|
||||
Arrays.copyOfRange(payload, 38 + (i * 20), 44 + (i * 20)));
|
||||
int leg = (int) payload[29 + (i * 20)];
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
package seng302.visualiser;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.text.DateFormat;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.PriorityBlockingQueue;
|
||||
import javafx.animation.AnimationTimer;
|
||||
import javafx.beans.property.ReadOnlyBooleanProperty;
|
||||
import javafx.collections.ObservableList;
|
||||
@@ -21,8 +23,9 @@ import javafx.scene.paint.Color;
|
||||
import javafx.scene.shape.Polygon;
|
||||
import javafx.scene.text.Text;
|
||||
import seng302.model.Limit;
|
||||
import seng302.visualiser.fxObjects.BoatGroup;
|
||||
import seng302.visualiser.fxObjects.MarkGroup;
|
||||
import seng302.visualiser.fxObjects.AnnotationBox;
|
||||
import seng302.visualiser.fxObjects.BoatObject;
|
||||
import seng302.visualiser.fxObjects.MarkObject;
|
||||
import seng302.model.Colors;
|
||||
import seng302.model.Boat;
|
||||
import seng302.model.map.Boundary;
|
||||
@@ -32,10 +35,6 @@ import seng302.model.mark.Mark;
|
||||
import seng302.model.mark.MarkType;
|
||||
import seng302.model.mark.SingleMark;
|
||||
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.GeoUtility;
|
||||
|
||||
@@ -65,8 +64,9 @@ public class GameView extends Pane {
|
||||
private double metersPerPixelX;
|
||||
private double metersPerPixelY;
|
||||
|
||||
private List<MarkGroup> markGroups = new ArrayList<>();
|
||||
private List<BoatGroup> boatGroups = new ArrayList<>();
|
||||
private Map<SingleMark, MarkObject> markObjects = new HashMap<>();
|
||||
private Map<Boat, BoatObject> boatObjects = new HashMap<>();
|
||||
private List<AnnotationBox> annotations = new ArrayList<>();
|
||||
|
||||
private Text fpsDisplay = new Text();
|
||||
|
||||
@@ -130,17 +130,14 @@ public class GameView extends Pane {
|
||||
};
|
||||
}
|
||||
|
||||
void initializeCanvas() {
|
||||
|
||||
fitMarksToCanvas();
|
||||
public void initializeCanvas() {
|
||||
drawGoogleMap();
|
||||
fpsDisplay.setLayoutX(5);
|
||||
fpsDisplay.setLayoutY(20);
|
||||
fpsDisplay.setStrokeWidth(2);
|
||||
gameObjects.add(fpsDisplay);
|
||||
gameObjects.add(raceBorder);
|
||||
initializeMarks();
|
||||
initializeBoats();
|
||||
|
||||
this.widthProperty().addListener(resize -> {
|
||||
canvasWidth = this.getWidth();
|
||||
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
|
||||
* in a compound mark etc..
|
||||
*/
|
||||
private void addRaceBorder() {
|
||||
XMLParser.RaceXMLObject raceXMLObject = StreamParser.getXmlObject().getRaceXML();
|
||||
ArrayList<Limit> courseLimits = raceXMLObject.getCourseLimit();
|
||||
public void updateBorder(List<Limit> border) {
|
||||
raceBorder.setStroke(new Color(0.0f, 0.0f, 0.74509807f, 1));
|
||||
raceBorder.setStrokeWidth(3);
|
||||
raceBorder.setFill(new Color(0,0,0,0));
|
||||
List<Double> boundaryPoints = new ArrayList<>();
|
||||
for (Limit limit : courseLimits) {
|
||||
for (Limit limit : border) {
|
||||
Point2D location = findScaledXY(limit.getLat(), limit.getLng());
|
||||
boundaryPoints.add(location.getX());
|
||||
boundaryPoints.add(location.getY());
|
||||
@@ -222,112 +217,114 @@ public class GameView extends Pane {
|
||||
}
|
||||
|
||||
private void updateGroups() {
|
||||
for (BoatGroup boatGroup : boatGroups) {
|
||||
// some raceObjects will have multiple ID's (for instance gate marks)
|
||||
//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();
|
||||
boatObjects.forEach((boat, boatObject) -> {});
|
||||
markObjects.forEach((mark, markObject) -> {});
|
||||
}
|
||||
|
||||
private void checkForCourseChanges() {
|
||||
if (StreamParser.isNewRaceXmlReceived()){
|
||||
addRaceBorder();
|
||||
}
|
||||
}
|
||||
|
||||
private void updateBoatGroup(BoatGroup boatGroup) {
|
||||
// PriorityBlockingQueue<PositionUpdateData> movementQueue = StreamParser.boatLocations.get(boatGroup.getRaceId());
|
||||
// // giving the movementQueue a 5 packet buffer to account for slightly out of order packets
|
||||
// if (movementQueue.size() > 0) {
|
||||
// private void updateBoatGroup(BoatGroup boatGroup) {
|
||||
//// PriorityBlockingQueue<PositionUpdateData> movementQueue = StreamParser.boatLocations.get(boatGroup.getRaceId());
|
||||
//// // giving the movementQueue a 5 packet buffer to account for slightly out of order packets
|
||||
//// if (movementQueue.size() > 0) {
|
||||
//// try {
|
||||
//// PositionUpdateData positionPacket = movementQueue.take();
|
||||
// Point2D p2d = findScaledXY(positionPacket.getLat(), positionPacket.getLon());
|
||||
//// double heading = 360.0 / 0xffff * positionPacket.getHeading();
|
||||
// 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){
|
||||
// try {
|
||||
// PositionUpdateData positionPacket = movementQueue.take();
|
||||
Point2D p2d = findScaledXY(positionPacket.getLat(), positionPacket.getLon());
|
||||
// double heading = 360.0 / 0xffff * positionPacket.getHeading();
|
||||
boatGroup.setDestination(
|
||||
p2d.getX(), p2d.getY(), heading, positionPacket.getGroundSpeed(),
|
||||
positionPacket.getTimeValid(), frameRate);
|
||||
// } catch (InterruptedException e){
|
||||
// Point2D p2d = findScaledXY(positionPacket.getLat(), positionPacket.getLon());
|
||||
// markGroup.moveMarkTo(p2d.getX(), p2d.getY(), raceId);
|
||||
// } catch (InterruptedException e) {
|
||||
// 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.
|
||||
*/
|
||||
private void initializeBoats() {
|
||||
Map<Integer, Boat> boats = StreamParser.getBoats();
|
||||
Group wakes = new Group();
|
||||
Group trails = new Group();
|
||||
Group annotations = new Group();
|
||||
public void setBoats(List<Boat> boats) {
|
||||
Group annotationsGroup = new Group();
|
||||
Group wakesGroup = new Group();
|
||||
Group boatObjectGroup = new Group();
|
||||
|
||||
ArrayList<Participant> participants = StreamParser.getXmlObject().getRaceXML()
|
||||
.getParticipants();
|
||||
ArrayList<Integer> participantIDs = new ArrayList<>();
|
||||
for (Participant p : participants) {
|
||||
participantIDs.add(p.getsourceID());
|
||||
}
|
||||
BoatObject newObject;
|
||||
for (Boat boat : boats) {
|
||||
newObject = new BoatObject();
|
||||
// newObject.bindBoat(boat);
|
||||
newObject.setFill(Colors.getColor());
|
||||
createAnnotationBox(boat);
|
||||
|
||||
for (Boat boat : boats.values()) {
|
||||
if (participantIDs.contains(boat.getSourceID())) {
|
||||
boat.setColour(Colors.getColor());
|
||||
BoatGroup boatGroup = new BoatGroup(boat, boat.getColour());
|
||||
boatGroups.add(boatGroup);
|
||||
trails.getChildren().add(boatGroup.getTrail());
|
||||
wakes.getChildren().add(boatGroup.getWake());
|
||||
annotations.getChildren().add(boatGroup.getAnnotations());
|
||||
}
|
||||
}
|
||||
gameObjects.addAll(trails);
|
||||
gameObjects.addAll(wakes);
|
||||
gameObjects.addAll(annotations);
|
||||
gameObjects.addAll(boatGroups);
|
||||
// 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());
|
||||
}
|
||||
|
||||
private void initializeMarks() {
|
||||
List<Mark> allMarks = StreamParser.getXmlObject().getRaceXML().getNonDupCompoundMarks();
|
||||
for (Mark mark : allMarks) {
|
||||
private AnnotationBox createAnnotationBox (Boat boat) {
|
||||
AnnotationBox newAnnotation;
|
||||
newAnnotation = new AnnotationBox();
|
||||
newAnnotation.addAnnotation("name", boat.getShortName());
|
||||
// newAnnotation.addAnnotation("country", boat.getCountry());
|
||||
newAnnotation.addAnnotation(
|
||||
"velocity",
|
||||
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);
|
||||
}
|
||||
);
|
||||
annotations.add(newAnnotation);
|
||||
return newAnnotation;
|
||||
}
|
||||
|
||||
public void updateCourse(List<Mark> course) {
|
||||
for (Mark mark : course) {
|
||||
if (mark.getMarkType() == MarkType.SINGLE_MARK) {
|
||||
SingleMark sMark = (SingleMark) mark;
|
||||
|
||||
MarkGroup markGroup = new MarkGroup(sMark, findScaledXY(sMark));
|
||||
markGroups.add(markGroup);
|
||||
MarkObject markObject = new MarkObject(sMark, findScaledXY(sMark));
|
||||
markObjects.put(sMark, markObject);
|
||||
} else {
|
||||
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.
|
||||
markGroups.add(markGroup);
|
||||
// markObjects.put(markObject.);
|
||||
}
|
||||
}
|
||||
gameObjects.addAll(markGroups);
|
||||
// gameObjects.addAll(markObjects);
|
||||
}
|
||||
|
||||
private void drawFps(int fps){
|
||||
@@ -340,12 +337,11 @@ public class GameView extends Pane {
|
||||
*/
|
||||
private void fitMarksToCanvas() {
|
||||
//Check is called once to avoid unnecessarily change the course limits once the race is running
|
||||
StreamParser.isNewRaceXmlReceived();
|
||||
findMinMaxPoint();
|
||||
double minLonToMaxLon = scaleRaceExtremities();
|
||||
calculateReferencePointLocation(minLonToMaxLon);
|
||||
//givePointsXY();
|
||||
addRaceBorder();
|
||||
// updateBorder();
|
||||
}
|
||||
|
||||
|
||||
@@ -356,9 +352,9 @@ public class GameView extends Pane {
|
||||
*/
|
||||
private void findMinMaxPoint() {
|
||||
List<Limit> sortedPoints = new ArrayList<>();
|
||||
for (Limit limit : raceData) {
|
||||
sortedPoints.add(limit);
|
||||
}
|
||||
// for (Limit limit : ) {
|
||||
// sortedPoints.add(limit);
|
||||
// }
|
||||
sortedPoints.sort(Comparator.comparingDouble(Limit::getLat));
|
||||
Limit minLatMark = sortedPoints.get(0);
|
||||
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,
|
||||
boolean legTime, boolean trail, boolean wake) {
|
||||
for (BoatGroup boatGroup : boatGroups) {
|
||||
boatGroup.setVisibility(teamName, velocity, estTime, legTime, trail, wake);
|
||||
for (BoatObject boatObject : boatObjects.values()) {
|
||||
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();
|
||||
}
|
||||
|
||||
public void selectBoat (int boatId) {
|
||||
|
||||
public void selectBoat (Boat selectedBoat) {
|
||||
boatObjects.forEach((boat, group) ->
|
||||
group.setIsSelected(boat == selectedBoat)
|
||||
);
|
||||
}
|
||||
|
||||
public void pauseRace () {
|
||||
@@ -527,8 +531,4 @@ public class GameView extends Pane {
|
||||
public void startRace () {
|
||||
timer.start();
|
||||
}
|
||||
|
||||
public void updateBorder (List<Limit> courseLimit) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,16 +1,21 @@
|
||||
package seng302.visualiser.controllers;
|
||||
|
||||
import com.sun.javafx.collections.SortableList;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import javafx.animation.KeyFrame;
|
||||
import javafx.animation.Timeline;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.collections.transformation.SortedList;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.fxml.FXMLLoader;
|
||||
import javafx.geometry.Point2D;
|
||||
import javafx.scene.Scene;
|
||||
|
||||
import javafx.scene.chart.LineChart;
|
||||
import javafx.scene.chart.NumberAxis;
|
||||
import javafx.scene.chart.XYChart;
|
||||
import javafx.scene.chart.XYChart.Data;
|
||||
import javafx.scene.chart.XYChart.Series;
|
||||
import javafx.scene.control.Button;
|
||||
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.ImportantAnnotationDelegate;
|
||||
import seng302.visualiser.controllers.annotations.ImportantAnnotationsState;
|
||||
import seng302.visualiser.fxObjects.BoatGroup;
|
||||
import seng302.visualiser.fxObjects.MarkGroup;
|
||||
import seng302.visualiser.fxObjects.BoatObject;
|
||||
import seng302.visualiser.fxObjects.MarkObject;
|
||||
import seng302.model.*;
|
||||
import seng302.model.mark.GateMark;
|
||||
import seng302.model.mark.Mark;
|
||||
@@ -45,15 +50,14 @@ import seng302.model.stream.parsers.StreamParser;
|
||||
|
||||
import java.io.IOException;
|
||||
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 {
|
||||
|
||||
@FXML
|
||||
private LineChart raceSparkLine;
|
||||
private LineChart<String, Double> raceSparkLine;
|
||||
@FXML
|
||||
private NumberAxis sparklineYAxis;
|
||||
@FXML
|
||||
@@ -75,15 +79,14 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel
|
||||
|
||||
//Race Data
|
||||
private Map<Integer, Boat> participants;
|
||||
private Map<Integer, Mark> course;
|
||||
private RaceXMLData raceData;
|
||||
private Map<Integer, Mark> markers;
|
||||
private RaceXMLData courseData;
|
||||
private GameView gameView;
|
||||
private RaceState raceState;
|
||||
|
||||
private Timeline timerTimeline;
|
||||
private Stage stage;
|
||||
private HashMap<Integer, Series<String, Double>> sparkLineData = new HashMap<>();
|
||||
private ImportantAnnotationsState importantAnnotations;
|
||||
private Boat selectedBoat;
|
||||
|
||||
public void initialize() {
|
||||
// Load a default important annotation state
|
||||
@@ -96,20 +99,26 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel
|
||||
raceSparkLine.getYAxis().setAutoRanging(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();
|
||||
initialiseFPSCheckBox();
|
||||
initialiseAnnotationSlider();
|
||||
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.setBoats(new ArrayList<>(participants.values()));
|
||||
gameView.updateBorder(raceData.getCourseLimit());
|
||||
gameView.updateCourse(new ArrayList<>(raceData.getCompoundMarks().values()));
|
||||
gameView.startRace();
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -169,7 +178,6 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel
|
||||
if (n == 2) {
|
||||
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())
|
||||
);
|
||||
annotationSlider.setValue(2);
|
||||
@@ -202,10 +210,11 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel
|
||||
private void updateSparkLine(){
|
||||
// Collect the racing boats that aren't already in the chart
|
||||
List<Boat> sparkLineCandidates = new ArrayList<>();
|
||||
participants.forEach((id, boat) ->{
|
||||
if (!sparkLineData.containsKey(id) && boat.getPosition() != null && !boat.getPosition().equals("-"))
|
||||
sparkLineCandidates.add(boat);
|
||||
});
|
||||
// participants.forEach((id, boat) ->{
|
||||
// if (!sparkLineData.containsKey(id) && boat.getPosition() != null && !boat.getPosition().equals("-"))
|
||||
// sparkLineCandidates.add(boat);
|
||||
// });
|
||||
participants.forEach((id, boat) -> sparkLineCandidates.add(boat));
|
||||
|
||||
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 -> {
|
||||
Series<String, Double> yachtData = new Series<>();
|
||||
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);
|
||||
});
|
||||
|
||||
// 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());
|
||||
Collections.sort(positions, (o1, o2) -> {
|
||||
positions.sort((o1, o2) -> {
|
||||
Integer leg1 = Integer.parseInt(o1.getData().get(o1.getData().size()-1).getXValue());
|
||||
Integer leg2 = Integer.parseInt(o2.getData().get(o2.getData().size()-1).getXValue());
|
||||
if (leg2 < leg1){
|
||||
@@ -247,13 +261,21 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel
|
||||
* @param legNumber the leg number that the position will be assigned to
|
||||
*/
|
||||
public void updateYachtPositionSparkline(Boat boat, Integer legNumber){
|
||||
XYChart.Series<String, Double> positionData = sparkLineData.get(boat.getSourceID());
|
||||
positionData.getData().add(
|
||||
new XYChart.Data<>(
|
||||
Integer.toString(legNumber),
|
||||
1 + participants.size() - Double.parseDouble(boat.getPosition())
|
||||
)
|
||||
);
|
||||
for (XYChart.Series<String, Double> positionData : sparkLineData.values()) {
|
||||
positionData.getData().add(
|
||||
new Data<>(
|
||||
Integer.toString(legNumber),
|
||||
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
|
||||
* @return The next Mark or null if none found
|
||||
*/
|
||||
private Mark getNextMark(BoatGroup bg) {
|
||||
private Mark getNextMark(BoatObject bg) {
|
||||
|
||||
Integer legNumber = bg.getBoat().getLegNumber();
|
||||
List<Corner> markSequence = raceData.getMarkSequence();
|
||||
List<Corner> markSequence = courseData.getMarkSequence();
|
||||
|
||||
if (legNumber == 0) {
|
||||
return null;
|
||||
@@ -319,7 +341,7 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel
|
||||
|
||||
for (Corner corner : markSequence) {
|
||||
if (legNumber + 2 == corner.getSeqID()) {
|
||||
return raceData.getCompoundMarks().get(corner.getCompoundMarkID());
|
||||
return courseData.getCompoundMarks().get(corner.getCompoundMarkID());
|
||||
}
|
||||
}
|
||||
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
|
||||
*/
|
||||
private void updateWindDirection() {
|
||||
windDirectionText.setText(String.format("%.1f°", StreamParser.getWindDirection()));
|
||||
windArrowText.setRotate(StreamParser.getWindDirection());
|
||||
windDirectionText.setText(String.format("%.1f°", raceState.getWindDirection()));
|
||||
windArrowText.setRotate(raceState.getWindDirection());
|
||||
}
|
||||
|
||||
|
||||
@@ -339,26 +361,14 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel
|
||||
* Updates the clock for the race
|
||||
*/
|
||||
private void updateRaceTime() {
|
||||
if (StreamParser.isRaceFinished()) {
|
||||
if (!raceState.isRaceStarted()) {
|
||||
timerLabel.setFill(Color.RED);
|
||||
timerLabel.setText("Race Finished!");
|
||||
} 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
|
||||
* section
|
||||
@@ -369,101 +379,98 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel
|
||||
positionVbox.getStylesheets().add(getClass().getResource("/css/master.css").toString());
|
||||
|
||||
// list of racing boat id
|
||||
List<Boat> sorted = new ArrayList<>(participants.values());
|
||||
sorted.sort(Comparator.comparingInt(Boat::getPosition));
|
||||
|
||||
if (StreamParser.isRaceStarted()) {
|
||||
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
|
||||
Text textToAdd = new Text(boat.getPosition() + ". " +
|
||||
boat.getShortName() + " (Finished)");
|
||||
textToAdd.setFill(Paint.valueOf("#d3d3d3"));
|
||||
positionVbox.getChildren().add(textToAdd);
|
||||
for (Boat boat : sorted) {
|
||||
if (boat.getBoatStatus() == 3) { // 3 is finish status
|
||||
Text textToAdd = new Text(boat.getPosition() + ". " +
|
||||
boat.getShortName() + " (Finished)");
|
||||
textToAdd.setFill(Paint.valueOf("#d3d3d3"));
|
||||
positionVbox.getChildren().add(textToAdd);
|
||||
|
||||
} else {
|
||||
Text textToAdd = new Text(boat.getPosition() + ". " +
|
||||
boat.getShortName() + " ");
|
||||
textToAdd.setFill(Paint.valueOf("#d3d3d3"));
|
||||
textToAdd.setStyle("");
|
||||
positionVbox.getChildren().add(textToAdd);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
participants.forEach((id, boat) ->{
|
||||
} else {
|
||||
Text textToAdd = new Text(boat.getPosition() + ". " +
|
||||
boat.getShortName() + " ");
|
||||
textToAdd.setFill(Paint.valueOf("#d3d3d3"));
|
||||
textToAdd.setStyle("");
|
||||
positionVbox.getChildren().add(textToAdd);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void updateLaylines(BoatGroup bg) {
|
||||
|
||||
Mark nextMark = getNextMark(bg);
|
||||
Boolean isUpwind = null;
|
||||
// Can only calc leg direction if there is a next mark and it is a gate mark
|
||||
if (nextMark != null) {
|
||||
if (nextMark instanceof GateMark) {
|
||||
if (bg.isUpwindLeg(gameViewController, nextMark)) {
|
||||
isUpwind = true;
|
||||
} else {
|
||||
isUpwind = false;
|
||||
}
|
||||
|
||||
for(MarkGroup mg : gameViewController.getMarkGroups()) {
|
||||
|
||||
mg.removeLaylines();
|
||||
|
||||
if (mg.getMainMark().getId() == nextMark.getId()) {
|
||||
|
||||
SingleMark singleMark1 = ((GateMark) nextMark).getSingleMark1();
|
||||
SingleMark singleMark2 = ((GateMark) nextMark).getSingleMark2();
|
||||
Point2D markPoint1 = gameViewController
|
||||
.findScaledXY(singleMark1.getLatitude(), singleMark1.getLongitude());
|
||||
Point2D markPoint2 = gameViewController
|
||||
.findScaledXY(singleMark2.getLatitude(), singleMark2.getLongitude());
|
||||
HashMap<Double, Double> angleAndSpeed;
|
||||
if (isUpwind) {
|
||||
angleAndSpeed = PolarTable.getOptimalUpwindVMG(StreamParser.getWindSpeed());
|
||||
} else {
|
||||
angleAndSpeed = PolarTable.getOptimalDownwindVMG(StreamParser.getWindSpeed());
|
||||
}
|
||||
|
||||
Double resultingAngle = angleAndSpeed.keySet().iterator().next();
|
||||
|
||||
|
||||
Point2D boatCurrentPos = new Point2D(bg.getBoatLayoutX(), bg.getBoatLayoutY());
|
||||
Point2D gateMidPoint = markPoint1.midpoint(markPoint2);
|
||||
Integer lineFuncResult = GeoUtility.lineFunction(boatCurrentPos, gateMidPoint, markPoint2);
|
||||
Line rightLayline = new Line();
|
||||
Line leftLayline = new Line();
|
||||
if (lineFuncResult == 1) {
|
||||
rightLayline = makeRightLayline(markPoint2, 180 - resultingAngle, StreamParser.getWindDirection());
|
||||
leftLayline = makeLeftLayline(markPoint1, 180 - resultingAngle, StreamParser.getWindDirection());
|
||||
} else if (lineFuncResult == -1) {
|
||||
rightLayline = makeRightLayline(markPoint1, 180 - resultingAngle, StreamParser.getWindDirection());
|
||||
leftLayline = makeLeftLayline(markPoint2, 180 - resultingAngle, StreamParser.getWindDirection());
|
||||
}
|
||||
|
||||
leftLayline.setStrokeWidth(0.5);
|
||||
leftLayline.setStroke(bg.getBoat().getColour());
|
||||
|
||||
rightLayline.setStrokeWidth(0.5);
|
||||
rightLayline.setStroke(bg.getBoat().getColour());
|
||||
|
||||
bg.setLaylines(leftLayline, rightLayline);
|
||||
mg.addLaylines(leftLayline, rightLayline);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// participants.forEach((id, boat) ->{
|
||||
// Text textToAdd = new Text(boat.getPosition() + ". " +
|
||||
// boat.getShortName() + " ");
|
||||
// textToAdd.setFill(Paint.valueOf("#d3d3d3"));
|
||||
// textToAdd.setStyle("");
|
||||
// positionVbox.getChildren().add(textToAdd);
|
||||
// });
|
||||
}
|
||||
|
||||
|
||||
// private void updateLaylines(BoatObject bg) {
|
||||
//
|
||||
// Mark nextMark = getNextMark(bg);
|
||||
// Boolean isUpwind = null;
|
||||
// // Can only calc leg direction if there is a next mark and it is a gate mark
|
||||
// if (nextMark != null) {
|
||||
// if (nextMark instanceof GateMark) {
|
||||
// if (bg.isUpwindLeg(gameViewController, nextMark)) {
|
||||
// isUpwind = true;
|
||||
// } else {
|
||||
// isUpwind = false;
|
||||
// }
|
||||
//
|
||||
// for(MarkObject mg : gameViewController.getMarkGroups()) {
|
||||
//
|
||||
// mg.removeLaylines();
|
||||
//
|
||||
// if (mg.getMainMark().getId() == nextMark.getId()) {
|
||||
//
|
||||
// SingleMark singleMark1 = ((GateMark) nextMark).getSingleMark1();
|
||||
// SingleMark singleMark2 = ((GateMark) nextMark).getSingleMark2();
|
||||
// Point2D markPoint1 = gameViewController
|
||||
// .findScaledXY(singleMark1.getLatitude(), singleMark1.getLongitude());
|
||||
// Point2D markPoint2 = gameViewController
|
||||
// .findScaledXY(singleMark2.getLatitude(), singleMark2.getLongitude());
|
||||
// HashMap<Double, Double> angleAndSpeed;
|
||||
// if (isUpwind) {
|
||||
// angleAndSpeed = PolarTable.getOptimalUpwindVMG(StreamParser.getWindSpeed());
|
||||
// } else {
|
||||
// angleAndSpeed = PolarTable.getOptimalDownwindVMG(StreamParser.getWindSpeed());
|
||||
// }
|
||||
//
|
||||
// Double resultingAngle = angleAndSpeed.keySet().iterator().next();
|
||||
//
|
||||
//
|
||||
// Point2D boatCurrentPos = new Point2D(bg.getBoatLayoutX(), bg.getBoatLayoutY());
|
||||
// Point2D gateMidPoint = markPoint1.midpoint(markPoint2);
|
||||
// Integer lineFuncResult = GeoUtility.lineFunction(boatCurrentPos, gateMidPoint, markPoint2);
|
||||
// Line rightLayline = new Line();
|
||||
// Line leftLayline = new Line();
|
||||
// if (lineFuncResult == 1) {
|
||||
// rightLayline = makeRightLayline(markPoint2, 180 - resultingAngle, StreamParser.getWindDirection());
|
||||
// leftLayline = makeLeftLayline(markPoint1, 180 - resultingAngle, StreamParser.getWindDirection());
|
||||
// } else if (lineFuncResult == -1) {
|
||||
// rightLayline = makeRightLayline(markPoint1, 180 - resultingAngle, StreamParser.getWindDirection());
|
||||
// leftLayline = makeLeftLayline(markPoint2, 180 - resultingAngle, StreamParser.getWindDirection());
|
||||
// }
|
||||
//
|
||||
// leftLayline.setStrokeWidth(0.5);
|
||||
// leftLayline.setStroke(bg.getBoat().getColour());
|
||||
//
|
||||
// rightLayline.setStrokeWidth(0.5);
|
||||
// rightLayline.setStroke(bg.getBoat().getColour());
|
||||
//
|
||||
// bg.setLaylines(leftLayline, rightLayline);
|
||||
// mg.addLaylines(leftLayline, rightLayline);
|
||||
//
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
|
||||
private Point2D getPointRotation(Point2D ref, Double distance, Double angle){
|
||||
Double newX = ref.getX() + (ref.getX() + distance -ref.getX())*Math.cos(angle) - (ref.getY() + distance -ref.getY())*Math.sin(angle);
|
||||
Double newY = ref.getY() + (ref.getX() + distance -ref.getX())*Math.sin(angle) + (ref.getY() + distance -ref.getY())*Math.cos(angle);
|
||||
@@ -495,17 +502,27 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel
|
||||
* 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) {
|
||||
Boat thisBoat = (Boat) newValue;
|
||||
setSelectedBoat(thisBoat);
|
||||
boatSelectionComboBox.setItems(
|
||||
FXCollections.observableArrayList(participants.values())
|
||||
);
|
||||
//Null check is if the listener is fired but nothing selected
|
||||
boatSelectionComboBox.valueProperty().addListener((obs, lastSelection, selectedBoat) -> {
|
||||
if (selectedBoat != null) {
|
||||
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
|
||||
@@ -525,38 +542,12 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Convert seconds to a string of the format mm:ss
|
||||
*
|
||||
* @param time the time in seconds
|
||||
* @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 String getMillisToFormattedTime(long milliseconds) {
|
||||
return String.format("%02d:%02d:%02d",
|
||||
TimeUnit.MILLISECONDS.toHours(milliseconds),
|
||||
TimeUnit.MILLISECONDS.toMinutes(milliseconds) % 60, //Modulus 60 minutes per hour
|
||||
TimeUnit.MILLISECONDS.toSeconds(milliseconds) % 60 //Modulus 60 seconds per minute
|
||||
);
|
||||
}
|
||||
|
||||
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
|
||||
*/
|
||||
private void setSelectedBoat(Boat boat) {
|
||||
for (BoatGroup bg : gameViewController.getBoatGroups()) {
|
||||
//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(boat.getHullID())) {
|
||||
updateLaylines(bg);
|
||||
bg.setIsSelected(true);
|
||||
selectedBoat = boat;
|
||||
} else {
|
||||
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);
|
||||
// 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
|
||||
// //are to toggle its annotations, there is no other backwards knowledge of a yacht to its boatgroup.
|
||||
// if (bg.getBoat().getHullID().equals(boat.getHullID())) {
|
||||
//// updateLaylines(bg);
|
||||
// bg.setIsSelected(true);
|
||||
//// selectedBoat = boat;
|
||||
// } else {
|
||||
// bg.setIsSelected(false);
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
public void updateRaceData (RaceXMLData raceData) {
|
||||
this.raceData = raceData;
|
||||
this.courseData = raceData;
|
||||
gameView.updateBorder(raceData.getCourseLimit());
|
||||
}
|
||||
|
||||
|
||||
@@ -61,7 +61,6 @@ public class StartScreenController {
|
||||
ClientToServerThread clientToServerThread = new ClientToServerThread("localhost", 4950);
|
||||
// controller.setClientToServerThread(clientToServerThread);
|
||||
clientToServerThread.start();
|
||||
// new GameServerThread("Fuck you");
|
||||
// get the lobby controller so that we can pass the game server thread to it
|
||||
setContentPane("/views/LobbyView.fxml");
|
||||
|
||||
|
||||
@@ -1,20 +1,18 @@
|
||||
package seng302.visualiser.controllers.client;
|
||||
|
||||
import java.text.DateFormat;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.io.IOException;
|
||||
import java.time.ZoneId;
|
||||
import java.time.ZoneOffset;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.TimeZone;
|
||||
import javafx.fxml.FXMLLoader;
|
||||
import javafx.scene.layout.Pane;
|
||||
import seng302.model.Boat;
|
||||
import seng302.model.RaceState;
|
||||
import seng302.model.mark.Mark;
|
||||
import seng302.model.stream.parsers.PositionUpdateData.DeviceType;
|
||||
import seng302.model.stream.parsers.MarkRoundingData;
|
||||
import seng302.model.stream.parsers.RaceStartData;
|
||||
import seng302.model.stream.parsers.RaceStatusData;
|
||||
import seng302.model.stream.parsers.xml.RaceXMLData;
|
||||
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.PositionUpdateData;
|
||||
import seng302.model.stream.packets.StreamPacket;
|
||||
import seng302.visualiser.ClientSocketListener;
|
||||
import seng302.visualiser.ClientToServerThread;
|
||||
import seng302.visualiser.controllers.RaceViewController;
|
||||
|
||||
@@ -31,35 +28,37 @@ import seng302.visualiser.controllers.RaceViewController;
|
||||
*/
|
||||
public class ClientController {
|
||||
|
||||
private final DateFormat DATE_TIME_FORMAT = new SimpleDateFormat("dd/MM/yyyy HH:mm:ss");
|
||||
|
||||
private Pane holderPane;
|
||||
private ClientToServerThread socketThread;
|
||||
private ClientSocketListener socketListener;
|
||||
|
||||
private RaceViewController raceView;
|
||||
|
||||
private Map<Integer, Boat> allBoatsMap;
|
||||
private Map<Integer, Boat> racingBoats = new HashMap<>();
|
||||
private RegattaXMLData regattaData;
|
||||
private RaceXMLData raceData;
|
||||
private RaceXMLData courseData;
|
||||
private RaceState raceState = new RaceState();
|
||||
|
||||
public ClientController (String ipAddress, Pane holder) {
|
||||
this.holderPane = holder;
|
||||
socketThread = new ClientToServerThread(ipAddress, 4950);
|
||||
socketThread.start();
|
||||
socketListener = this::parsePacket;
|
||||
socketThread.addStreamObserver(socketListener);
|
||||
socketThread.addStreamObserver(this::parsePacket);
|
||||
}
|
||||
|
||||
private void loadRaceView () {
|
||||
allBoatsMap.forEach((id, boat) -> {
|
||||
if (raceData.getParticipants().contains(id))
|
||||
if (courseData.getParticipants().contains(id))
|
||||
racingBoats.put(id, boat);
|
||||
});
|
||||
FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("RaceView.fxml"));
|
||||
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) {
|
||||
@@ -72,49 +71,47 @@ public class ClientController {
|
||||
regattaData = XMLParser.parseRegatta(
|
||||
StreamParser.extractXmlMessage(packet)
|
||||
);
|
||||
DATE_TIME_FORMAT.setTimeZone(
|
||||
raceState.setTimeZone(
|
||||
TimeZone.getTimeZone(
|
||||
ZoneId.ofOffset("UTC", ZoneOffset.ofHours(regattaData.getUtcOffset()))
|
||||
)
|
||||
);
|
||||
startRaceIfAllDataRecieved();
|
||||
startRaceIfAllDataReceived();
|
||||
break;
|
||||
|
||||
case RACE_XML:
|
||||
raceData = XMLParser.parseRace(
|
||||
courseData = XMLParser.parseRace(
|
||||
StreamParser.extractXmlMessage(packet)
|
||||
);
|
||||
if (raceView != null) {
|
||||
raceView.updateRaceData(raceData);
|
||||
raceView.updateRaceData(courseData);
|
||||
}
|
||||
startRaceIfAllDataRecieved();
|
||||
startRaceIfAllDataReceived();
|
||||
break;
|
||||
|
||||
case BOAT_XML:
|
||||
allBoatsMap = XMLParser.parseBoats(
|
||||
StreamParser.extractXmlMessage(packet)
|
||||
);
|
||||
startRaceIfAllDataRecieved();
|
||||
startRaceIfAllDataReceived();
|
||||
break;
|
||||
|
||||
case RACE_START_STATUS:
|
||||
RaceStartData raceStartData = StreamParser.extractRaceStartStatus(packet);
|
||||
raceState.updateState(StreamParser.extractRaceStartStatus(packet));
|
||||
break;
|
||||
|
||||
case BOAT_LOCATION:
|
||||
PositionUpdateData positionData = StreamParser.extractBoatLocation(packet);
|
||||
updatePosition(positionData);
|
||||
updatePosition(StreamParser.extractBoatLocation(packet));
|
||||
break;
|
||||
|
||||
case MARK_ROUNDING:
|
||||
MarkRoundingData roundingData = StreamParser.extractMarkRounding(packet);
|
||||
updateMarkRounding(roundingData);
|
||||
updateMarkRounding(StreamParser.extractMarkRounding(packet));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void startRaceIfAllDataRecieved () {
|
||||
if (raceData != null && allBoatsMap != null && regattaData != null)
|
||||
private void startRaceIfAllDataReceived() {
|
||||
if (courseData != null && allBoatsMap != null && regattaData != null)
|
||||
loadRaceView();
|
||||
}
|
||||
|
||||
@@ -129,8 +126,8 @@ public class ClientController {
|
||||
boat.setLat(positionData.getLat());
|
||||
boat.setLon(positionData.getLon());
|
||||
boat.setHeading(positionData.getHeading());
|
||||
} else {
|
||||
Mark mark = raceData.getCompoundMarks().get(positionData.getDeviceId());
|
||||
} else if (positionData.getType() == DeviceType.MARK_TYPE) {
|
||||
Mark mark = courseData.getCompoundMarks().get(positionData.getDeviceId());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -142,21 +139,19 @@ public class ClientController {
|
||||
private void updateMarkRounding(MarkRoundingData roundingData) {
|
||||
Boat boat = racingBoats.get(roundingData.getBoatId());
|
||||
boat.setMarkRoundingTime(roundingData.getTimeStamp());
|
||||
boat.setTimeSinceLastMark(raceState.getRaceTime() - roundingData.getTimeStamp());
|
||||
boat.setLastMarkRounded(
|
||||
raceData.getCompoundMarks().get(
|
||||
courseData.getCompoundMarks().get(
|
||||
roundingData.getMarkId()
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
private void processRaceStatusUpdate (RaceStatusData data) {
|
||||
String raceTimeStr = DATE_TIME_FORMAT.format(data.getCurrentTime());
|
||||
Date date = new Date();
|
||||
date.getTime();
|
||||
long timeTillStart = (data.getExpectedStartTime() - data.getCurrentTime()) / 1000;
|
||||
raceState.updateState(data);
|
||||
for (long[] boatData : data.getBoatData()) {
|
||||
Boat boat = allBoatsMap.get((int) boatData[0]);
|
||||
boat.setEstimateTimeAtNextMark(boatData[1]);
|
||||
boat.setEstimateTimeTillNextMark(raceState.getRaceTime() - boatData[1]);
|
||||
boat.setEstimateTimeAtFinish(boatData[2]);
|
||||
int legNumber = (int) boatData[3];
|
||||
boat.setLegNumber(legNumber);
|
||||
@@ -172,6 +167,10 @@ public class ClientController {
|
||||
}
|
||||
}
|
||||
|
||||
private void close () {
|
||||
socketThread.closeSocket();
|
||||
}
|
||||
|
||||
// /** Handle the key-pressed event from the text field. */
|
||||
// public void keyPressed(KeyEvent e) {
|
||||
// BoatActionMessage boatActionMessage;
|
||||
@@ -201,7 +200,7 @@ public class ClientController {
|
||||
// break;
|
||||
// }
|
||||
// }
|
||||
//
|
||||
|
||||
// public void keyReleased(KeyEvent e) {
|
||||
// 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)
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
+58
-82
@@ -6,10 +6,10 @@ import javafx.geometry.Point2D;
|
||||
import javafx.scene.CacheHint;
|
||||
import javafx.scene.Group;
|
||||
import javafx.scene.paint.Color;
|
||||
import javafx.scene.paint.Paint;
|
||||
import javafx.scene.shape.Line;
|
||||
import javafx.scene.shape.Polygon;
|
||||
import javafx.scene.transform.Rotate;
|
||||
import seng302.visualiser.controllers.GameViewController;
|
||||
import seng302.model.Boat;
|
||||
import seng302.utilities.GeoUtility;
|
||||
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
|
||||
* maximised.
|
||||
*/
|
||||
public class BoatGroup extends Group {
|
||||
public class BoatObject extends Group {
|
||||
|
||||
//Constants for drawing
|
||||
private static final double BOAT_HEIGHT = 15d;
|
||||
@@ -47,79 +47,57 @@ public class BoatGroup extends Group {
|
||||
private Double distanceTravelled = 0.0;
|
||||
private Point2D lastPoint;
|
||||
private boolean destinationSet;
|
||||
private BoatAnnotations boatAnnotations;
|
||||
private AnnotationBox annotationBox;
|
||||
|
||||
private Paint colour = Color.BLACK;
|
||||
|
||||
private Boolean isSelected = true; //All boats are initialised as selected
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
destinationSet = false;
|
||||
this.boat = boat;
|
||||
initChildren(color);
|
||||
public BoatObject() {
|
||||
this(-BOAT_WIDTH / 2, BOAT_HEIGHT / 2,
|
||||
0.0, -BOAT_HEIGHT / 2,
|
||||
BOAT_WIDTH / 2, BOAT_HEIGHT / 2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a BoatGroup with the boat being the default polygon. The head of the boat should be
|
||||
* 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
|
||||
* polygon.
|
||||
*/
|
||||
public BoatGroup(Boat boat, Color color, 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) {
|
||||
public BoatObject(double... points) {
|
||||
boatPoly = new Polygon(points);
|
||||
boatPoly.setFill(color);
|
||||
boatPoly.setFill(colour);
|
||||
boatPoly.setOnMouseEntered(event -> {
|
||||
boatPoly.setFill(Color.FLORALWHITE);
|
||||
boatPoly.setStroke(Color.RED);
|
||||
});
|
||||
boatPoly.setOnMouseExited(event -> {
|
||||
boatPoly.setFill(color);
|
||||
boatPoly.setFill(colour);
|
||||
boatPoly.setStroke(Color.BLACK);
|
||||
});
|
||||
boatPoly.setOnMouseClicked(event -> setIsSelected(!isSelected));
|
||||
boatPoly.setCache(true);
|
||||
boatPoly.setCacheHint(CacheHint.SPEED);
|
||||
boatAnnotations = new BoatAnnotations(boat, color);
|
||||
|
||||
annotationBox = new AnnotationBox();
|
||||
annotationBox.setFill(colour);
|
||||
|
||||
leftLayLine = new Line();
|
||||
rightLayline = new Line();
|
||||
|
||||
wake = new Wake(0, -BOAT_HEIGHT);
|
||||
super.getChildren().addAll(boatPoly, boatAnnotations);
|
||||
super.getChildren().addAll(boatPoly, annotationBox);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
private void initChildren(Color color) {
|
||||
initChildren(color,
|
||||
-BOAT_WIDTH / 2, BOAT_HEIGHT / 2,
|
||||
0.0, -BOAT_HEIGHT / 2,
|
||||
BOAT_WIDTH / 2, BOAT_HEIGHT / 2);
|
||||
public void setFill (Paint value) {
|
||||
this.colour = value;
|
||||
boatPoly.setFill(colour);
|
||||
annotationBox.setFill(colour);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -132,8 +110,8 @@ public class BoatGroup extends Group {
|
||||
private void moveGroupBy(double dx, double dy) {
|
||||
boatPoly.setLayoutX(boatPoly.getLayoutX() + dx);
|
||||
boatPoly.setLayoutY(boatPoly.getLayoutY() + dy);
|
||||
boatAnnotations.setLayoutX(boatAnnotations.getLayoutX() + dx);
|
||||
boatAnnotations.setLayoutY(boatAnnotations.getLayoutY() + dy);
|
||||
annotationBox.setLayoutX(annotationBox.getLayoutX() + dx);
|
||||
annotationBox.setLayoutY(annotationBox.getLayoutY() + dy);
|
||||
wake.setLayoutX(wake.getLayoutX() + dx);
|
||||
wake.setLayoutY(wake.getLayoutY() + dy);
|
||||
}
|
||||
@@ -149,8 +127,8 @@ public class BoatGroup extends Group {
|
||||
rotateTo(rotation);
|
||||
boatPoly.setLayoutX(x);
|
||||
boatPoly.setLayoutY(y);
|
||||
boatAnnotations.setLayoutX(x);
|
||||
boatAnnotations.setLayoutY(y);
|
||||
annotationBox.setLayoutX(x);
|
||||
annotationBox.setLayoutY(y);
|
||||
wake.setLayoutX(x);
|
||||
wake.setLayoutY(y);
|
||||
wake.rotate(rotation);
|
||||
@@ -225,54 +203,52 @@ public class BoatGroup extends Group {
|
||||
lastTimeValid = timeValid;
|
||||
isStopped = false;
|
||||
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
|
||||
* 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
|
||||
* going up wind, if they are on different sides of the gate, then the boat is going downwind
|
||||
* @param canvasController
|
||||
*/
|
||||
public Boolean isUpwindLeg(GameViewController canvasController, Mark nextMark) {
|
||||
|
||||
Double windAngle = StreamParser.getWindDirection();
|
||||
GateMark thisGateMark = (GateMark) nextMark;
|
||||
SingleMark nextMark1 = thisGateMark.getSingleMark1();
|
||||
SingleMark nextMark2 = thisGateMark.getSingleMark2();
|
||||
Point2D nextMarkPoint1 = canvasController.findScaledXY(nextMark1.getLatitude(), nextMark1.getLongitude());
|
||||
Point2D nextMarkPoint2 = canvasController.findScaledXY(nextMark2.getLatitude(), nextMark2.getLongitude());
|
||||
|
||||
Point2D boatCurrentPoint = new Point2D(boatPoly.getLayoutX(), boatPoly.getLayoutY());
|
||||
Point2D windTestPoint = GeoUtility.makeArbitraryVectorPoint(nextMarkPoint1, windAngle, 10d);
|
||||
|
||||
|
||||
Integer boatLineFuncResult = GeoUtility.lineFunction(nextMarkPoint1, nextMarkPoint2, boatCurrentPoint);
|
||||
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
|
||||
boat is travelling into the wind. thus upwind. Otherwise if they are on different sides, then the boat is going
|
||||
with the wind.
|
||||
*/
|
||||
return boatLineFuncResult.equals(windLineFuncResult);
|
||||
}
|
||||
// /**
|
||||
// * 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
|
||||
// * 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
|
||||
// * @param canvasController
|
||||
// */
|
||||
// public Boolean isUpwindLeg(GameViewController canvasController, Mark nextMark) {
|
||||
//
|
||||
// Double windAngle = StreamParser.getWindDirection();
|
||||
// GateMark thisGateMark = (GateMark) nextMark;
|
||||
// SingleMark nextMark1 = thisGateMark.getSingleMark1();
|
||||
// SingleMark nextMark2 = thisGateMark.getSingleMark2();
|
||||
// Point2D nextMarkPoint1 = canvasController.findScaledXY(nextMark1.getLatitude(), nextMark1.getLongitude());
|
||||
// Point2D nextMarkPoint2 = canvasController.findScaledXY(nextMark2.getLatitude(), nextMark2.getLongitude());
|
||||
//
|
||||
// Point2D boatCurrentPoint = new Point2D(boatPoly.getLayoutX(), boatPoly.getLayoutY());
|
||||
// Point2D windTestPoint = GeoUtility.makeArbitraryVectorPoint(nextMarkPoint1, windAngle, 10d);
|
||||
//
|
||||
//
|
||||
// Integer boatLineFuncResult = GeoUtility.lineFunction(nextMarkPoint1, nextMarkPoint2, boatCurrentPoint);
|
||||
// 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
|
||||
// boat is travelling into the wind. thus upwind. Otherwise if they are on different sides, then the boat is going
|
||||
// with the wind.
|
||||
// */
|
||||
// return boatLineFuncResult.equals(windLineFuncResult);
|
||||
// return true;
|
||||
// }
|
||||
|
||||
public void setIsSelected(Boolean isSelected) {
|
||||
this.isSelected = isSelected;
|
||||
setLineGroupVisible(isSelected);
|
||||
setWakeVisible(isSelected);
|
||||
boatAnnotations.setVisible(isSelected);
|
||||
annotationBox.setVisible(isSelected);
|
||||
setLayLinesVisible(isSelected);
|
||||
}
|
||||
|
||||
public void setVisibility (boolean teamName, boolean velocity, boolean estTime, boolean legTime,
|
||||
boolean trail, boolean wake) {
|
||||
|
||||
boatAnnotations.setVisibile(teamName, velocity, estTime, legTime);
|
||||
this.wake.setVisible(wake);
|
||||
this.lineGroup.setVisible(trail);
|
||||
}
|
||||
@@ -324,7 +300,7 @@ public class BoatGroup extends Group {
|
||||
}
|
||||
|
||||
public Group getAnnotations() {
|
||||
return boatAnnotations;
|
||||
return annotationBox;
|
||||
}
|
||||
|
||||
public Double getBoatLayoutX() {
|
||||
+3
-3
@@ -16,7 +16,7 @@ import seng302.model.mark.SingleMark;
|
||||
/**
|
||||
* 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 LINE_THICKNESS = 2;
|
||||
@@ -31,7 +31,7 @@ public class MarkGroup extends Group {
|
||||
* @param mark
|
||||
* @param points
|
||||
*/
|
||||
public MarkGroup (SingleMark mark, Point2D points) {
|
||||
public MarkObject(SingleMark mark, Point2D points) {
|
||||
marks.add(mark);
|
||||
mainMark = mark;
|
||||
Color color = Color.BLACK;
|
||||
@@ -73,7 +73,7 @@ public class MarkGroup extends Group {
|
||||
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.getSingleMark2());
|
||||
mainMark = mark;
|
||||
Reference in New Issue
Block a user