Merge remote-tracking branch 'origin/develop' into 38b_LayLines

This commit is contained in:
William Muir
2017-05-22 15:40:11 +12:00
32 changed files with 498 additions and 1711 deletions
+4 -2
View File
@@ -5,8 +5,8 @@ import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;
import seng302.models.parsers.StreamParser;
import seng302.models.parsers.StreamReceiver;
import seng302.models.stream.StreamParser;
import seng302.models.stream.StreamReceiver;
import seng302.server.ServerThread;
public class App extends Application {
@@ -18,6 +18,7 @@ public class App extends Application {
primaryStage.setScene(new Scene(root));
primaryStage.setMaximized(true);
primaryStage.show();
primaryStage.setOnCloseRequest(e -> {
StreamParser.appClose();
@@ -64,6 +65,7 @@ public class App extends Application {
else{
// sr = new StreamReceiver("localhost", 4949, "RaceStream");
sr = new StreamReceiver("livedata.americascup.com", 4941, "RaceStream");
// sr = new StreamReceiver("csse-s302staff.canterbury.ac.nz", 4941, "RaceStream");
}
sr.start();
@@ -1,6 +1,6 @@
package seng302.controllers;
import javafx.animation.AnimationTimer;
import javafx.animation.*;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.fxml.FXML;
import javafx.geometry.Point2D;
@@ -10,22 +10,14 @@ import javafx.scene.canvas.GraphicsContext;
import javafx.scene.layout.AnchorPane;
import javafx.scene.paint.Color;
import javafx.scene.text.Font;
import seng302.models.BoatGroup;
import seng302.models.Colors;
import seng302.models.RaceObject;
import seng302.models.Yacht;
import seng302.models.*;
import seng302.models.mark.*;
import seng302.models.parsers.StreamParser;
import seng302.models.parsers.XMLParser;
import seng302.models.parsers.XMLParser.RaceXMLObject.CompoundMark;
import seng302.models.parsers.XMLParser.RaceXMLObject.Limit;
import seng302.models.parsers.packets.BoatPositionPacket;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import seng302.models.stream.StreamParser;
import seng302.models.stream.packets.BoatPositionPacket;
import seng302.models.stream.XMLParser;
import seng302.models.stream.XMLParser.RaceXMLObject.Limit;
import seng302.models.mark.Mark;
import java.util.*;
import java.util.concurrent.PriorityBlockingQueue;
/**
@@ -50,6 +42,7 @@ public class CanvasController {
private final int RHS_BUFFER = BUFFER_SIZE + MARK_SIZE / 2;
private final int TOP_BUFFER = BUFFER_SIZE;
private final int BOT_BUFFER = TOP_BUFFER + MARK_SIZE / 2;
private boolean horizontalInversion = false;
private double distanceScaleFactor;
private ScaleDirection scaleDirection;
@@ -59,16 +52,15 @@ public class CanvasController {
private Mark maxLonPoint;
private double referencePointX;
private double referencePointY;
private double metersToPixels;
private List<RaceObject> raceObjects = new ArrayList<>();
private List<Mark> raceMarks = new ArrayList<>();
private List<MarkGroup> markGroups = new ArrayList<>();
private List<BoatGroup> boatGroups = new ArrayList<>();
//FRAME RATE
private static final double UPDATE_TIME = 0.016666; // 1 / 60 ie 60fps
private Double frameRate = 60.0;
private final long[] frameTimes = new long[30];
private int frameTimeIndex = 0;
private boolean arrayFilled = false;
private DecimalFormat decimalFormat2dp = new DecimalFormat("0.00");
public AnimationTimer timer;
@@ -91,8 +83,6 @@ public class CanvasController {
// Bind canvas size to stack pane size.
canvas.widthProperty().bind(new SimpleDoubleProperty(CANVAS_WIDTH));
canvas.heightProperty().bind(new SimpleDoubleProperty(CANVAS_HEIGHT));
//group.minWidth(CANVAS_WIDTH);
//group.minHeight(CANVAS_HEIGHT);
}
public void initializeCanvas (){
@@ -106,7 +96,7 @@ public class CanvasController {
// TODO: 1/05/17 wmu16 - Change this call to now draw the marks as from the xml
drawBoats();
initializeBoats();
timer = new AnimationTimer() {
@Override
@@ -123,13 +113,12 @@ public class CanvasController {
if (arrayFilled) {
elapsedNanos = now - oldFrameTime ;
long elapsedNanosPerFrame = elapsedNanos / frameTimes.length ;
Double frameRate = 1_000_000_000.0 / elapsedNanosPerFrame ;
frameRate = 1_000_000_000.0 / elapsedNanosPerFrame ;
drawFps(frameRate.intValue());
}
// TODO: 1/05/17 cir27 - Make the RaceObjects update on the actual delay.
elapsedNanos = 1000 / 60;
updateRaceObjects();
updateGroups();
if (StreamParser.isRaceFinished()) {
this.stop();
}
@@ -178,65 +167,21 @@ public class CanvasController {
gc.fillPolygon(xBoundaryPoints,yBoundaryPoints,yBoundaryPoints.length);
}
/**
* Adds the course marks to the canvas, taken from the XMl file
*
* NOTE: This is quite confusing as objects are grabbed from the XMLParser such as Mark and CompoundMark which are
* named the same as those in the model package but are, however not the 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 addCourseMarks() {
XMLParser.RaceXMLObject raceXMLObject = StreamParser.getXmlObject().getRaceXML();
ArrayList<CompoundMark> compoundMarks = raceXMLObject.getCompoundMarks();
RaceObject markGroup;
for (CompoundMark compoundMark : compoundMarks) {
//If the compound mark has 2 marks then its a gate mark
if (compoundMark.getMarks().size() == 2) {
CompoundMark.Mark mark1 = compoundMark.getMarks().get(0);
CompoundMark.Mark mark2 = compoundMark.getMarks().get(1);
SingleMark singleMark1 = new SingleMark(mark1.getMarkName(), mark1.getTargetLat(), mark1.getTargetLng(), mark1.getSourceID());
SingleMark singleMark2 = new SingleMark(mark1.getMarkName(), mark2.getTargetLat(), mark2.getTargetLng(), mark2.getSourceID());
GateMark thisGateMark = new GateMark(compoundMark.getcMarkName(),
(compoundMark.getMarkID().equals(1)) ? MarkType.OPEN_GATE : MarkType.CLOSED_GATE,
singleMark1,
singleMark2,
singleMark1.getLatitude(),
singleMark1.getLongitude());
markGroup = new MarkGroup(thisGateMark,
findScaledXY(thisGateMark.getSingleMark1()),
findScaledXY(thisGateMark.getSingleMark2()));
raceObjects.add(markGroup);
raceMarks.add(thisGateMark);
//Otherwise its a single mark
} else {
CompoundMark.Mark singleMark = compoundMark.getMarks().get(0);
Mark thisSingleMark = new SingleMark(singleMark.getMarkName(),
singleMark.getTargetLat(),
singleMark.getTargetLng(),
singleMark.getSourceID());
markGroup = new MarkGroup(thisSingleMark, findScaledXY(thisSingleMark));
raceObjects.add(markGroup);
raceMarks.add(thisSingleMark);
}
}
}
private void updateRaceObjects(){
for (RaceObject raceObject : raceObjects) {
raceObject.updatePosition(1000 / 60);
// some raceObjects will have multiply ID's (for instance gate marks)
for (long id : raceObject.getRaceIds()) {
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.boatPositions.containsKey(boatGroup.getRaceId())) {
if (boatGroup.isStopped()) {
updateBoatGroup(boatGroup);
}
}
boatGroup.move();
}
for (MarkGroup markGroup : markGroups) {
for (int id : markGroup.getRaceIds()) {
if (StreamParser.boatPositions.containsKey(id)) {
move(id, raceObject);
updateMarkGroup(id, markGroup);
}
}
}
@@ -253,31 +198,50 @@ public class CanvasController {
}
}
private void move(long id, RaceObject raceObject){
PriorityBlockingQueue<BoatPositionPacket> movementQueue = StreamParser.boatPositions.get(id);
private void updateBoatGroup(BoatGroup boatGroup) {
PriorityBlockingQueue<BoatPositionPacket> movementQueue = StreamParser.boatPositions.get(boatGroup.getRaceId());
// giving the movementQueue a 5 packet buffer to account for slightly out of order packets
if (movementQueue.size() > 0){
BoatPositionPacket positionPacket = movementQueue.peek();
//this code adds a delay to reading from the movementQueue
//in case things being put into the movement queue are slightly
//out of order
int delayTime = 1000;
int loopTime = delayTime * 10;
long timeDiff = (System.currentTimeMillis()%loopTime - positionPacket.getTimeValid()%loopTime);
if (timeDiff < 0){
timeDiff = loopTime + timeDiff;
}
if (timeDiff > delayTime) {
try {
positionPacket = movementQueue.take();
Point2D p2d = latLonToXY(positionPacket.getLat(), positionPacket.getLon());
BoatPositionPacket positionPacket = movementQueue.take();
Point2D p2d = findScaledXY(positionPacket.getLat(), positionPacket.getLon());
double heading = 360.0 / 0xffff * positionPacket.getHeading();
raceObject.setDestination(p2d.getX(), p2d.getY(), heading, positionPacket.getGroundSpeed(), (int) id);
boatGroup.setDestination(p2d.getX(), p2d.getY(), heading, positionPacket.getGroundSpeed(), positionPacket.getTimeValid(), frameRate, boatGroup.getRaceId());
} catch (InterruptedException e){
e.printStackTrace();
}
// }
}
}
void updateMarkGroup (int raceId, MarkGroup markGroup) {
PriorityBlockingQueue<BoatPositionPacket> movementQueue = StreamParser.boatPositions.get(raceId);
if (movementQueue.size() > 0){
try {
BoatPositionPacket 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, Yacht> boats = StreamParser.getBoats();
Group boatAnnotations = new Group();
for (Yacht boat : boats.values()) {
boat.setColour(Colors.getColor());
BoatGroup boatGroup = new BoatGroup(boat, boat.getColour());
boatGroups.add(boatGroup);
boatAnnotations.getChildren().add(boatGroup.getLowPriorityAnnotations());
}
group.getChildren().add(boatAnnotations);
group.getChildren().addAll(boatGroups);
}
class ResizableCanvas extends Canvas {
@@ -305,11 +269,11 @@ public class CanvasController {
public double prefWidth(double height) {
return getWidth();
}
@Override
public double prefHeight(double width) {
return getHeight();
}
}
private void drawFps(int fps){
@@ -328,30 +292,6 @@ public class CanvasController {
}
}
/**
* Draws all the boats.
*/
private void drawBoats() {
// Map<Boat, TimelineInfo> timelineInfos = raceViewController.getTimelineInfos();
// List<Boat> boats = raceViewController.getStartingBoats();
Map<Integer, Yacht> boats = StreamParser.getBoats();
Double startingX = raceObjects.get(0).getLayoutX();
Double startingY = raceObjects.get(0).getLayoutY();
Group boatAnnotations = new Group();
for (Yacht boat : boats.values()) {
// for (Boat boat : boats) {
boat.setColour(Colors.getColor());
BoatGroup boatGroup = new BoatGroup(boat, boat.getColour());
boatGroup.moveTo(startingX, startingY, 0d);
//boatGroup.setStage(raceViewController.getStage());
raceObjects.add(boatGroup);
boatAnnotations.getChildren().add(boatGroup.getLowPriorityAnnotations());
}
group.getChildren().add(boatAnnotations);
group.getChildren().addAll(raceObjects);
}
/**
* Calculates x and y location for every marker that fits it to the canvas the race will be drawn on.
*/
@@ -361,9 +301,8 @@ public class CanvasController {
findMinMaxPoint();
double minLonToMaxLon = scaleRaceExtremities();
calculateReferencePointLocation(minLonToMaxLon);
givePointsXY();
//givePointsXY();
addRaceBorder();
findMetersToPixels();
}
@@ -386,16 +325,11 @@ public class CanvasController {
//If the course is on a point on the earth where longitudes wrap around.
Limit minLonMark = sortedPoints.get(0);
Limit maxLonMark = sortedPoints.get(sortedPoints.size()-1);
SingleMark thisMinLon = new SingleMark(minLonMark.toString(), minLonMark.getLat(), minLonMark.getLng(), minLonMark.getSeqID());
SingleMark thisMaxLon = new SingleMark(maxLonMark.toString(), maxLonMark.getLat(), maxLonMark.getLng(), maxLonMark.getSeqID());
// TODO: 30/03/17 cir27 - Correctly account for longitude wrapping around.
if (thisMaxLon.getLongitude() - thisMinLon.getLongitude() > 180) {
SingleMark temp = thisMinLon;
thisMinLon = thisMaxLon;
thisMaxLon = temp;
minLonPoint = new SingleMark(minLonMark.toString(), minLonMark.getLat(), minLonMark.getLng(), minLonMark.getSeqID());
maxLonPoint = new SingleMark(maxLonMark.toString(), maxLonMark.getLat(), maxLonMark.getLng(), maxLonMark.getSeqID());
if (maxLonPoint.getLongitude() - minLonPoint.getLongitude() > 180) {
horizontalInversion = true;
}
minLonPoint = thisMinLon;
maxLonPoint = thisMaxLon;
}
/**
@@ -426,7 +360,11 @@ public class CanvasController {
referencePointX += distanceScaleFactor * Math.sin(referenceAngle) * Mark.calculateDistance(referencePoint, minLonPoint);
referencePointX += ((CANVAS_WIDTH - (LHS_BUFFER + RHS_BUFFER)) - (minLonToMaxLon * distanceScaleFactor)) / 2;
}
if(horizontalInversion) {
referencePointX = CANVAS_WIDTH - RHS_BUFFER - (referencePointX - LHS_BUFFER);
}
}
/**
* Finds the scale factor necessary to fit all race markers within the onscreen map and assigns it to distanceScaleFactor
@@ -456,43 +394,18 @@ public class CanvasController {
return horiDistance;
}
/**
* Give all markers in the course an x,y location relative to a given reference with a known x,y location. Distances
* are scaled according to the distanceScaleFactor variable.
*/
private void givePointsXY() {
List<Mark> allPoints = new ArrayList<>(raceViewController.getRace().getCourse());
List<Mark> processed = new ArrayList<>();
RaceObject markGroup;
for (Mark mark : allPoints) {
if (!processed.contains(mark)) {
if (mark.getMarkType() != MarkType.SINGLE_MARK) {
GateMark gateMark = (GateMark) mark;
markGroup = new MarkGroup(mark, findScaledXY(gateMark.getSingleMark1()), findScaledXY(gateMark.getSingleMark2()));
raceObjects.add(markGroup);
} else {
markGroup = new MarkGroup(mark, findScaledXY(mark));
raceObjects.add(markGroup);
}
processed.add(mark);
}
}
}
private Point2D findScaledXY (Mark unscaled) {
return findScaledXY (minLatPoint.getLatitude(), minLatPoint.getLongitude(),
unscaled.getLatitude(), unscaled.getLongitude());
return findScaledXY (unscaled.getLatitude(), unscaled.getLongitude());
}
private Point2D findScaledXY (double latA, double lonA, double latB, double lonB) {
private Point2D findScaledXY (double unscaledLat, double unscaledLon) {
double distanceFromReference;
double angleFromReference;
int xAxisLocation = (int) referencePointX;
int yAxisLocation = (int) referencePointY;
angleFromReference = Mark.calculateHeadingRad(latA, lonA, latB, lonB);
distanceFromReference = Mark.calculateDistance(latA, lonA, latB, lonB);
angleFromReference = Mark.calculateHeadingRad(minLatPoint.getLatitude(), minLatPoint.getLongitude(), unscaledLat, unscaledLon);
distanceFromReference = Mark.calculateDistance(minLatPoint.getLatitude(), minLatPoint.getLongitude(), unscaledLat, unscaledLon);
if (angleFromReference >= 0 && angleFromReference <= Math.PI / 2) {
xAxisLocation += (int) Math.round(distanceScaleFactor * Math.sin(angleFromReference) * distanceFromReference);
yAxisLocation -= (int) Math.round(distanceScaleFactor * Math.cos(angleFromReference) * distanceFromReference);
@@ -509,44 +422,13 @@ public class CanvasController {
xAxisLocation -= (int) Math.round(distanceScaleFactor * Math.cos(angleFromReference) * distanceFromReference);
yAxisLocation += (int) Math.round(distanceScaleFactor * Math.sin(angleFromReference) * distanceFromReference);
}
if(horizontalInversion) {
xAxisLocation = CANVAS_WIDTH - RHS_BUFFER - (xAxisLocation - LHS_BUFFER);
}
return new Point2D(xAxisLocation, yAxisLocation);
}
/**
* Find the number of meters per pixel.
*/
private void findMetersToPixels () {
Double angularDistance;
Double angle;
Double straightLineDistance;
if (scaleDirection == ScaleDirection.HORIZONTAL) {
angularDistance = Mark.calculateDistance(minLonPoint, maxLonPoint);
angle = Mark.calculateHeadingRad(minLonPoint, maxLonPoint);
if (angle > Math.PI / 2) {
straightLineDistance = Math.cos(angle - Math.PI) * angularDistance;
} else {
straightLineDistance = Math.cos(angle) * angularDistance;
}
metersToPixels = (CANVAS_WIDTH - RHS_BUFFER - LHS_BUFFER) / straightLineDistance;
} else {
angularDistance = Mark.calculateDistance(minLatPoint, maxLatPoint);
angle = Mark.calculateHeadingRad(minLatPoint, maxLatPoint);
if (angle < Math.PI / 2) {
straightLineDistance = Math.cos(angle) * angularDistance;
} else {
straightLineDistance = Math.cos(-angle + Math.PI * 2) * angularDistance;
}
metersToPixels = (CANVAS_HEIGHT - TOP_BUFFER - BOT_BUFFER) / straightLineDistance;
}
}
private Point2D latLonToXY (double latitude, double longitude) {
return findScaledXY(minLatPoint.getLatitude(), minLatPoint.getLongitude(), latitude, longitude);
}
List<RaceObject> getRaceObjects() {
return raceObjects;
List<BoatGroup> getBoatGroups() {
return boatGroups;
}
}
@@ -9,6 +9,7 @@ import javafx.scene.layout.Pane;
import java.io.IOException;
import java.net.URL;
import java.util.ResourceBundle;
import seng302.models.stream.StreamParser;
public class Controller implements Initializable {
@@ -33,5 +34,6 @@ public class Controller implements Initializable {
public void initialize(URL location, ResourceBundle resources) {
contentPane.getStylesheets().add(getClass().getResource("/css/master.css").toString());
setContentPane("/views/StartScreenView.fxml");
StreamParser.boatPositions.clear();
}
}
@@ -1,82 +0,0 @@
package seng302.controllers;
import seng302.models.Race;
import seng302.models.Yacht;
import seng302.models.parsers.ConfigParser;
import seng302.models.parsers.CourseParser;
import seng302.models.parsers.StreamParser;
import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Map;
import java.util.Random;
/**
* Created by zyt10 on 17/03/17.
* run before CanvasController to initialize race events
* the CanvasController then uses the event data to make the animations
*/
public class RaceController {
Race race = null;
public void initializeRace() {
String raceConfigFile = "/config/config.xml";
String teamsConfigFile = "/config/teams.xml";
try {
race = createRace(raceConfigFile, teamsConfigFile); //These config files arent actually used
} catch (Exception e) {
System.out.println("There was an error creating the race.");
}
if (race != null) {
race.startRace();
} else {
System.out.println("There was an error creating the race. Exiting.");
}
}
public Race createRace(String configFile, String teamsConfigFile) throws Exception {
Race race = new Race();
// StreamParser.xmlObject
// Read team names from file
// TeamsParser tp = new TeamsParser(teamsConfigFile);
// Read course from file
// ConfigParser config = new ConfigParser(configFile);
ArrayList<String> boatNames = new ArrayList<>();
// ArrayList<Boat> teams = tp.getBoats();
Map<Long, Yacht> teams = StreamParser.getBoatsPos();
//get race size
int numberOfBoats = teams.size();
//get time scale
// double timeScale = config.getTimeScale();
// race.setTimeScale(timeScale);
for (Yacht boat : teams.values()) {
boatNames.add(boat.getBoatName());
race.addBoat(boat);
}
// Shuffle team names
long seed = System.nanoTime();
Collections.shuffle(boatNames, new Random(seed));
if (numberOfBoats > Array.getLength(boatNames.toArray())) {
return null;
}
CourseParser course = new CourseParser("/config/course.xml");
race.addCourse(course.getCourse());
return race;
}
public Race getRace() {
return race;
}
}
@@ -1,37 +0,0 @@
package seng302.controllers;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.VBox;
import javafx.scene.text.Text;
import seng302.models.Race;
import java.net.URL;
import java.util.ResourceBundle;
/**
* Created by ptg19 on 20/03/17.
*/
public class RaceResultController implements Initializable{
@FXML private AnchorPane window;
@FXML private VBox resultsVBox;
private Race race;
RaceResultController(Race race){
this.race = race;
}
@Override
public void initialize(URL location, ResourceBundle resources) {
int boatPosition = this.race.getFinishedBoats().length;
for (int i = this.race.getFinishedBoats().length - 1; i >= 0; i--){
resultsVBox.getChildren().add(0, new Text(boatPosition + ": " + this.race.getFinishedBoats()[i].getBoatName()));
boatPosition--;
}
}
}
@@ -26,7 +26,7 @@ import seng302.controllers.annotations.ImportantAnnotationController;
import seng302.controllers.annotations.ImportantAnnotationDelegate;
import seng302.controllers.annotations.ImportantAnnotationsState;
import seng302.models.*;
import seng302.models.parsers.StreamParser;
import seng302.models.stream.StreamParser;
import java.io.IOException;
import java.util.*;
@@ -58,7 +58,6 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel
private ArrayList<Yacht> startingBoats = new ArrayList<>();
private boolean displayFps;
private Timeline timerTimeline;
private Race race;
private Stage stage;
private ImportantAnnotationsState importantAnnotations;
@@ -68,13 +67,6 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel
// Load a default important annotation state
importantAnnotations = new ImportantAnnotationsState();
//Initialise race controller
RaceController raceController = new RaceController();
raceController.initializeRace();
race = raceController.getRace();
startingBoats = new ArrayList<>(Arrays.asList(race.getBoats()));
includedCanvasController.setup(this);
includedCanvasController.initializeCanvas();
initializeUpdateTimer();
@@ -113,7 +105,7 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel
// Load FXML and set CSS
fxmlLoader
.setLocation(getClass().getResource("/views/importantAnnotationSelectView.fxml"));
Scene scene = new Scene(fxmlLoader.load(), 469, 248);
Scene scene = new Scene(fxmlLoader.load(), 469, 298);
scene.getStylesheets().add(getClass().getResource("/css/master.css").toString());
stage.initStyle(StageStyle.UNDECORATED);
@@ -282,7 +274,6 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel
*/
private void loadRaceResultView() {
FXMLLoader loader = new FXMLLoader(getClass().getResource("/views/FinishView.fxml"));
loader.setController(new RaceResultController(race));
try {
contentAnchorPane.getChildren().removeAll();
@@ -335,11 +326,6 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel
return displayFps;
}
public Race getRace() {
return race;
}
/**
* Display the important annotations for a specific BoatGroup
* @param bg The boat group to set the annotations for
@@ -368,7 +354,7 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel
} else {
bg.setWakeVisible(false);
}
//TODO fix boat annotations with new boatgroup
if (importantAnnotations.getAnnotationState(Annotation.ESTTIMETONEXTMARK)) {
bg.setEstTimeToNextMarkObjectVisible(true);
} else {
@@ -386,9 +372,7 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel
switch (annotationLevel) {
// No Annotations
case 0:
for (RaceObject ro : includedCanvasController.getRaceObjects()) {
if (ro instanceof BoatGroup) {
BoatGroup bg = (BoatGroup) ro;
for (BoatGroup bg : includedCanvasController.getBoatGroups()) {
bg.setTeamNameObjectVisible(false);
bg.setVelocityObjectVisible(false);
bg.setEstTimeToNextMarkObjectVisible(false);
@@ -396,22 +380,16 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel
bg.setLineGroupVisible(false);
bg.setWakeVisible(false);
}
}
break;
// Important Annotations
case 1:
for (RaceObject ro : includedCanvasController.getRaceObjects()) {
if (ro instanceof BoatGroup) {
BoatGroup bg = (BoatGroup) ro;
for (BoatGroup bg : includedCanvasController.getBoatGroups()) {
setBoatGroupImportantAnnotations(bg);
}
}
break;
// All Annotations
case 2:
for (RaceObject ro : includedCanvasController.getRaceObjects()) {
if (ro instanceof BoatGroup) {
BoatGroup bg = (BoatGroup) ro;
for (BoatGroup bg : includedCanvasController.getBoatGroups()) {
bg.setTeamNameObjectVisible(true);
bg.setVelocityObjectVisible(true);
bg.setEstTimeToNextMarkObjectVisible(true);
@@ -419,7 +397,6 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel
bg.setLineGroupVisible(true);
bg.setWakeVisible(true);
}
}
break;
}
}
@@ -431,9 +408,7 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel
* @param yacht The yacht for which we want to view all annotations
*/
private void setSelectedBoat(Yacht yacht) {
for (RaceObject ro : includedCanvasController.getRaceObjects()) {
if (ro instanceof BoatGroup) {
BoatGroup bg = (BoatGroup) ro;
for (BoatGroup bg : includedCanvasController.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(yacht.getHullID())) {
@@ -444,7 +419,6 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel
}
}
}
}
void setStage(Stage stage) {
this.stage = stage;
@@ -22,7 +22,7 @@ import javafx.scene.layout.GridPane;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import seng302.models.Yacht;
import seng302.models.parsers.StreamParser;
import seng302.models.stream.StreamParser;
public class StartScreenController implements Initializable {
@FXML
@@ -58,10 +58,10 @@ public class StartScreenController implements Initializable {
contentPane.getChildren().addAll((Pane) FXMLLoader.load(getClass().getResource(jfxUrl)));
}
catch(javafx.fxml.LoadException e){
System.err.println(e.getCause());
e.printStackTrace();
}
catch(IOException e){
System.err.println(e);
e.printStackTrace();
}
}
@@ -132,6 +132,7 @@ public class StartScreenController implements Initializable {
}
public void switchToRaceView() {
StreamParser.boatPositions.clear();
switchedToRaceView = true;
setContentPane("/views/RaceView.fxml");
}
+194 -240
View File
@@ -1,29 +1,30 @@
package seng302.models;
import javafx.event.EventHandler;
import javafx.geometry.Point2D;
import javafx.scene.CacheHint;
import javafx.scene.Group;
import javafx.scene.input.MouseDragEvent;
import javafx.scene.input.MouseEvent;
import javafx.scene.paint.Color;
import javafx.scene.paint.Paint;
import javafx.scene.shape.Line;
import javafx.scene.shape.Polygon;
import javafx.scene.text.Text;
import javafx.scene.transform.Rotate;
import javafx.stage.Stage;
import seng302.models.parsers.StreamParser;
import seng302.models.stream.StreamParser;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.List;
/**
* BoatGroup is a javafx group that by default contains a graphical objects for representing a 2
* dimensional boat. It contains a single polygon for the boat, a group of lines to show it's path,
* a wake object and two text labels to annotate the boat teams name and the boats velocity. The
* boat will update it's position onscreen everytime UpdatePosition is called unless the window is
* minimized in which case it attempts to store animations and apply them when the window is
* maximised.
* BoatGroup is a javafx group that by default contains a graphical objects for representing a 2 dimensional boat.
* It contains a single polygon for the boat, a group of lines to show it's path, a wake object and two text labels to
* annotate the boat teams name and the boats velocity. The boat will update it's position onscreen everytime
* UpdatePosition is called unless the window is minimized in which case it attempts to store animations and apply them
* when the window is maximised.
*/
public class BoatGroup extends RaceObject {
public class BoatGroup extends Group{
//Constants for drawing
private static final double TEAMNAME_X_OFFSET = 10d;
@@ -37,9 +38,12 @@ public class BoatGroup extends RaceObject {
private static final double BOAT_HEIGHT = 15d;
private static final double BOAT_WIDTH = 10d;
//Variables for boat logic.
private Point2D lastPoint;
private int wakeGenerationDelay = 10;
private double distanceTravelled;
private boolean isStopped = true;
private double xIncrement;
private double yIncrement;
private long lastTimeValid = 0;
private Double lastRotation = 0.0;
private long framesToMove;
//Graphical objects
private Yacht boat;
private Group lineGroup = new Group();
@@ -49,70 +53,73 @@ public class BoatGroup extends RaceObject {
private Text estTimeToNextMarkObject;
private Text legTimeObject;
private Wake wake;
private boolean isSelected = true; //Boats annotations are visible by default at the start
//Handles boat moving when connecting to a stream
private boolean setToInitialLocation = false;
private Double distanceTravelled = 0.0;
private Point2D lastPoint;
private boolean destinationSet;
//Variables for handling minimization
private Stage stage;
private boolean isMaximized = true;
private List<Line> lineStorage = new ArrayList<>();
private int setCallCount = 5;
private Color textColor = Color.RED;
private Boolean isSelected = true; //All boats are initalised 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 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 (Yacht boat, Color color){
this.boat = boat;
initChildren(color);
this.textColor = color;
}
/**
* 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.
* 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.
* @param points An array of co-ordinates x1,y1,x2,y2,x3,y3... that will make up the boat polygon.
*/
public BoatGroup(Yacht boat, Color color, double... points) {
public BoatGroup (Yacht boat, Color color, double... points)
{
this.boat = boat;
initChildren(color, points);
}
/**
* 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.setCacheHint(CacheHint.SPEED);
text.setCache(true);
return text;
}
/**
* 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.
* @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) {
textColor = color;
destinationSet = false;
boatPoly = new Polygon(points);
boatPoly.setFill(color);
boatPoly.setOnMouseEntered(event -> boatPoly.setFill(Color.FLORALWHITE));
boatPoly.setOnMouseExited(event -> boatPoly.setFill(color));
boatPoly.setOnMouseClicked(event -> setIsSelected(!isSelected));
boatPoly.setCache(true);
boatPoly.setCacheHint(CacheHint.SPEED);
teamNameObject = new Text(boat.getShortName());
velocityObject = new Text(String.valueOf(boat.getVelocity()));
DateFormat format = new SimpleDateFormat("mm:ss");
String timeToNextMark = format
.format(boat.getEstimateTimeAtNextMark() - StreamParser.getCurrentTimeLong());
estTimeToNextMarkObject = new Text("Next mark: " + timeToNextMark);
if (boat.getMarkRoundingTime() != null) {
String elapsedTime = format
.format(StreamParser.getCurrentTimeLong() - boat.getMarkRoundingTime());
legTimeObject = new Text("Last mark: " + elapsedTime);
} else {
legTimeObject = new Text("Last mark: -");
}
teamNameObject = getTextObject(boat.getShortName(), textColor);
velocityObject = getTextObject(boat.getVelocity().toString(), textColor);
teamNameObject.setX(TEAMNAME_X_OFFSET);
teamNameObject.setY(TEAMNAME_Y_OFFSET);
@@ -121,17 +128,24 @@ public class BoatGroup extends RaceObject {
velocityObject.setX(VELOCITY_X_OFFSET);
velocityObject.setY(VELOCITY_Y_OFFSET);
velocityObject.relocate(velocityObject.getX(), velocityObject.getY());
destinationSet = false;
updateLastMarkRoundingTime();
updateTimeTillNextMark();
if (estTimeToNextMarkObject != null){
estTimeToNextMarkObject.setX(ESTTIMETONEXTMARK_X_OFFSET);
estTimeToNextMarkObject.setY(ESTTIMETONEXTMARK_Y_OFFSET);
estTimeToNextMarkObject
.relocate(estTimeToNextMarkObject.getX(), estTimeToNextMarkObject.getY());
}
if (legTimeObject != null){
legTimeObject.setX(LEGTIME_X_OFFSET);
legTimeObject.setY(LEGTIME_Y_OFFSET);
legTimeObject.relocate(legTimeObject.getX(), legTimeObject.getY());
}
wake = new Wake(0, -BOAT_HEIGHT);
super.getChildren()
.addAll(teamNameObject, velocityObject, boatPoly, estTimeToNextMarkObject,
@@ -140,7 +154,6 @@ public class BoatGroup extends RaceObject {
/**
* 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) {
@@ -151,13 +164,11 @@ public class BoatGroup extends RaceObject {
}
/**
* Moves the boat and its children annotations from its current coordinates by specified
* amounts.
*
* Moves the boat and its children annotations from its current coordinates by specified amounts.
* @param dx The amount to move the X coordinate by
* @param dy The amount to move the Y coordinate by
*/
public void moveGroupBy(double dx, double dy, double rotation) {
private void moveGroupBy(double dx, double dy) {
boatPoly.setLayoutX(boatPoly.getLayoutX() + dx);
boatPoly.setLayoutY(boatPoly.getLayoutY() + dy);
teamNameObject.setLayoutX(teamNameObject.getLayoutX() + dx);
@@ -170,28 +181,16 @@ public class BoatGroup extends RaceObject {
legTimeObject.setLayoutY(legTimeObject.getLayoutY() + dy);
wake.setLayoutX(wake.getLayoutX() + dx);
wake.setLayoutY(wake.getLayoutY() + dy);
rotateTo(rotation + currentRotation);
}
/**
* Moves the boat and its children annotations to coordinates specified
*
* @param x The X coordinate to move the boat to
* @param y The Y coordinate to move the boat to
* @param rotation The heading in degrees from north the boat should rotate to.
*/
public void moveTo(double x, double y, double rotation) {
private void moveTo(double x, double y, double rotation) {
rotateTo(rotation);
moveTo(x, y);
}
/**
* Moves the boat and its children annotations to coordinates specified
*
* @param x The X coordinate to move the boat to
* @param y The Y coordinate to move the boat to
*/
public void moveTo(double x, double y) {
boatPoly.setLayoutX(x);
boatPoly.setLayoutY(y);
teamNameObject.setLayoutX(x);
@@ -204,26 +203,65 @@ public class BoatGroup extends RaceObject {
legTimeObject.setLayoutY(y);
wake.setLayoutX(x);
wake.setLayoutY(y);
wake.rotate(currentRotation);
wake.rotate(rotation);
}
private void rotateTo(double rotation) {
boatPoly.getTransforms().setAll(new Rotate(rotation));
}
/**
* Updates the position of all graphics in the BoatGroup based off of the given time interval.
*
* @param timeInterval The interval, in milliseconds, the boat should update it's position based
* on.
* Updates the time until next mark label, will create a label if one doesn't exist
*/
public void updatePosition(long timeInterval) {
//Calculate the movement of the boat.
if (isMaximized) {
double dx = pixelVelocityX * timeInterval;
double dy = pixelVelocityY * timeInterval;
double rotation = rotationalVelocity * timeInterval;
private void updateTimeTillNextMark(){
if (estTimeToNextMarkObject == null){
estTimeToNextMarkObject = getTextObject("", textColor);
}
if (boat.getEstimateTimeAtNextMark() != null){
DateFormat format = new SimpleDateFormat("mm:ss");
String timeToNextMark = format
.format(boat.getEstimateTimeAtNextMark() - StreamParser.getCurrentTimeLong());
estTimeToNextMarkObject.setText("Next mark: " + timeToNextMark);
} else {
estTimeToNextMarkObject.setText("Next mark: -");
}
}
/**
* Updates the time since last mark rounding, will create a label if one doesn't exist
*/
private void updateLastMarkRoundingTime(){
if (legTimeObject == null){
legTimeObject = getTextObject("", textColor);
}
if (boat.getMarkRoundingTime() != null){
DateFormat format = new SimpleDateFormat("mm:ss");
String elapsedTime = format
.format(StreamParser.getCurrentTimeLong() - boat.getMarkRoundingTime());
legTimeObject.setText("Last mark: " + elapsedTime);
}
else{
legTimeObject.setText("Last mark: -");
}
}
public void move() {
double dx = xIncrement * framesToMove;
double dy = yIncrement * framesToMove;
distanceTravelled += Math.abs(dx) + Math.abs(dy);
moveGroupBy(dx, dy, rotation);
//Draw a new section of the trail every 20 pixels of movement.
if (distanceTravelled > 20) {
distanceTravelled = 0;
moveGroupBy(xIncrement, yIncrement);
framesToMove = framesToMove - 1;
if (framesToMove <= 0){
isStopped = true;
}
if (distanceTravelled > 70){
distanceTravelled = 0d;
if (lastPoint != null){
Line l = new Line(
lastPoint.getX(),
@@ -233,129 +271,96 @@ public class BoatGroup extends RaceObject {
);
l.getStrokeDashArray().setAll(3d, 7d);
l.setStroke(boat.getColour());
l.setCache(true);
l.setCacheHint(CacheHint.SPEED);
lineGroup.getChildren().add(l);
}
if (destinationSet) { //Only begin drawing after the first destination is set
if (destinationSet){
lastPoint = new Point2D(boatPoly.getLayoutX(), boatPoly.getLayoutY());
}
}
wake.updatePosition(timeInterval);
wake.updatePosition(1000/60);
}
/**
* Calculates the rotational velocity required to reach the rotationalGoal from the currentRotation.
*/
protected Double calculateRotationalVelocity (Double rotationalGoal) {
Double rotationalVelocity = 0.0;
if (Math.abs(rotationalGoal - lastRotation) > 180) {
if (rotationalGoal - lastRotation >= 0.0) {
rotationalVelocity = ((rotationalGoal - lastRotation) - 360) / 200;
} else {
rotationalVelocity = (360 + (rotationalGoal - lastRotation)) / 200;
}
} else {
rotationalVelocity = (rotationalGoal - lastRotation) / 200;
}
//Sometimes the rotation is too large to be realistic. In that case just do it instantly.
if (Math.abs(rotationalVelocity) > 1) {
rotationalVelocity = 0.0;
}
return rotationalVelocity;
}
/**
* Sets the destination of the boat and the headng it should have once it reaches
*
* @param newXValue The X co-ordinate the boat needs to move to.
* @param newYValue The Y co-ordinate the boat needs to move to.
* @param rotation Rotation to move graphics to.
* @param raceIds RaceID of the object to move.
* @param timeValid the time the position values are valid for
*/
public void setDestination(double newXValue, double newYValue, double rotation,
double groundSpeed, int... raceIds) {
if (hasRaceId(raceIds)) {
if (setToInitialLocation) {
destinationSet = true;
boat.setVelocity(groundSpeed);
if (currentRotation < 0) {
currentRotation = 360 - currentRotation;
}
double dx = newXValue - boatPoly.getLayoutX();
double dy = newYValue - boatPoly.getLayoutY();
//Check movement is reasonable. Assumes a 1000 * 1000 canvas
if (Math.abs(dx) > 50 || Math.abs(dy) > 50) {
dx = 0;
dy = 0;
moveTo(newXValue, newYValue);
}
pixelVelocityX = dx / expectedUpdateInterval;
pixelVelocityY = dy / expectedUpdateInterval;
rotationalGoal = rotation;
calculateRotationalVelocity();
if (wakeGenerationDelay > 0) {
wake.rotate(rotationalGoal);
rotateTo(rotationalGoal); //Need to test with this removed.
rotationalVelocity = 0;
wakeGenerationDelay--;
} else {
wake.setRotationalVelocity(rotationalVelocity, rotationalGoal,
boat.getVelocity());
}
velocityObject.setText(String.format("%.2f m/s", boat.getVelocity()));
DateFormat format = new SimpleDateFormat("mm:ss");
// estimate time to next mark
String timeToNextMark = format
.format(boat.getEstimateTimeAtNextMark() - StreamParser.getCurrentTimeLong());
estTimeToNextMarkObject.setText("Next mark: " + timeToNextMark);
// elapsed time
if (boat.getMarkRoundingTime() != null) {
String elapsedTime = format
.format(StreamParser.getCurrentTimeLong() - boat.getMarkRoundingTime());
legTimeObject.setText("Last mark: " + elapsedTime);
} else {
legTimeObject.setText("Last mark: -");
}
} else {
setToInitialLocation = true;
rotationalGoal = rotation;
public void setDestination (double newXValue, double newYValue, double rotation, double groundSpeed, long timeValid, double frameRate, long id) {
if (lastTimeValid == 0){
lastTimeValid = timeValid - 200;
moveTo(newXValue, newYValue, rotation);
}
}
//If minimized generate lines every 5 calls to set destination.
if (!isMaximized) {
setToInitialLocation = false;
wakeGenerationDelay = 2;
if (setCallCount-- == 0) {
setCallCount = 5;
if (lastPoint != null) {
Line l = new Line(
lastPoint.getX(),
lastPoint.getY(),
newXValue,
newYValue
);
l.getStrokeDashArray().setAll(3d, 7d);
l.setStroke(boatPoly.getFill());
lineStorage.add(l);
}
if (destinationSet) { //Only begin drawing after the first destination is set
lastPoint = new Point2D(newXValue, newYValue);
}
}
}
}
framesToMove = Math.round((frameRate/(1000.0f/(timeValid-lastTimeValid))));
double dx = newXValue - boatPoly.getLayoutX();
double dy = newYValue - boatPoly.getLayoutY();
xIncrement = dx/framesToMove;
yIncrement = dy/framesToMove;
public void setDestination(double newXValue, double newYValue, double groundSpeed,
int... raceIDs) {
destinationSet = true;
if (hasRaceId(raceIDs)) {
double rotation = Math.abs(
Math.toDegrees(
Math.atan(
(newYValue - boatPoly.getLayoutY()) / (newXValue - boatPoly.getLayoutX())
)
)
);
setDestination(newXValue, newYValue, rotation, groundSpeed, raceIDs);
}
Double rotationalVelocity = calculateRotationalVelocity(rotation);
updateTimeTillNextMark();
updateLastMarkRoundingTime();
if (Math.abs(rotationalVelocity) > 0.075) {
rotationalVelocity = 0.0;
wake.rotate(rotation);
}
public void rotateTo(double rotation) {
currentRotation = rotation;
boatPoly.getTransforms().setAll(new Rotate(rotation));
rotateTo(rotation);
wake.setRotationalVelocity(rotationalVelocity, groundSpeed);
velocityObject.setText(String.format("%.2f m/s", groundSpeed));
lastTimeValid = timeValid;
isStopped = false;
lastRotation = rotation;
}
public void forceRotation() {
rotateTo(rotationalGoal);
wake.rotate(rotationalGoal);
public void setIsSelected(Boolean isSelected) {
this.isSelected = isSelected;
setTeamNameObjectVisible(isSelected);
setVelocityObjectVisible(isSelected);
setLineGroupVisible(isSelected);
setWakeVisible(isSelected);
setEstTimeToNextMarkObjectVisible(isSelected);
setLegTimeObjectVisible(isSelected);
}
public void paintBoat(Color color) {
boatPoly.setFill(color);
}
public void setTeamNameObjectVisible(Boolean visible) {
teamNameObject.setVisible(visible);
@@ -385,52 +390,19 @@ public class BoatGroup extends RaceObject {
return boat;
}
/**
* This function sets the boats isSelected property AS WELL as actually acting upon the value of
* that selection. (Painting or not painting annotations)
*
* @param isSelected A Boolean indicating whether or not the boat is selected
*/
public void setIsSelected(Boolean isSelected) {
this.isSelected = isSelected;
setTeamNameObjectVisible(isSelected);
setVelocityObjectVisible(isSelected);
setLineGroupVisible(isSelected);
setWakeVisible(isSelected);
setEstTimeToNextMarkObjectVisible(isSelected);
setLegTimeObjectVisible(isSelected);
// TODO: 17/05/17 wmu16 - this should iterate over some list of annotations which we should make to easily make extensible
// paintBoat((isSelected) ? Color.WHITE : boat.getColour());
}
/**
* Returns true if this BoatGroup contains at least one of the given IDs.
*
* @param raceIds The ID's to check the BoatGroup for.
* @return True if the BoatGroup contains at east one of the given IDs, false otherwise.
*/
public boolean hasRaceId(int... raceIds) {
for (int id : raceIds) {
if (id == boat.getSourceID()) {
return true;
}
}
return false;
}
/**
* Returns all raceIds associated with this group. For BoatGroups the ID's are for the boat.
*
* @return An array containing all ID's associated with this RaceObject.
*/
public int[] getRaceIds() {
return new int[]{boat.getSourceID()};
public long getRaceId() {
return boat.getSourceID();
}
/**
* Due to javaFX limitations annotations associated with a boat that you want to appear below
* all boats in the Z-axis need to be pulled out of the BoatGroup and added to the parent group
* of the BoatGroups. This function returns these annotations as a group.
* Due to javaFX limitations annotations associated with a boat that you want to appear below all boats in the
* Z-axis need to be pulled out of the BoatGroup and added to the parent group of the BoatGroups. This function
* returns these annotations as a group.
*
* @return A group containing low priority annotations.
*/
@@ -440,26 +412,8 @@ public class BoatGroup extends RaceObject {
return group;
}
/**
* Use this function to let the BoatGroup know about the stage it is in. If it knows about it's
* stage then it will listen to the iconified property of that stage and change it's behaviour
* upon minimization. Without setting the Stage there is guarantee that the BoatGroup will draw
* properly when the stage is minimized.
*
* @param stage The stage that the BoatGroup is added to.
*/
public void setStage(Stage stage) {
/* TODO: 4/05/17 cir27 - Find a way to get the stage to this point. Need to pass it through multiple controllers.
App.start() -> Controller.setContentPane -> RaceViewController -> CanvasController
*/
this.stage = stage;
this.stage.iconifiedProperty().addListener(e -> {
isMaximized = !stage.isIconified();
if (!lineStorage.isEmpty()) {
lineGroup.getChildren().addAll(lineStorage);
lineStorage.clear();
}
});
public boolean isStopped() {
return isStopped;
}
@Override
+1 -1
View File
@@ -6,7 +6,7 @@ import javafx.scene.paint.Color;
* Created by ryan_ on 16/03/2017.
*/
public enum Colors {
RED, ORANGE, YELLOW, GREEN, BLUE, PURPLE;
RED, PERU, SEAGREEN, GREEN, BLUE, PURPLE;
static Integer index = 0;
-198
View File
@@ -1,198 +0,0 @@
package seng302.models;
import seng302.models.mark.Mark;
import java.util.*;
/**
* Race class containing the boats and legs in the race
* Created by mra106 on 8/3/2017.
*/
public class Race {
private ArrayList<Yacht> boats; // The boats in the race
private ArrayList<Yacht> finishingOrder; // The order in which the boats finish the race
private HashMap<Yacht, List> events = new HashMap<>(); // The events that occur in the race
private List<Mark> course; // Marks in the race
private long startTime = 0;
private double timeScale = 1;
private boolean raceFinished = false; // Race is finished
private int raceTime = -2; // Current time in the race
/**
* Race class containing the boats and legs in the race
*/
public Race() {
this.boats = new ArrayList<>();
this.finishingOrder = new ArrayList<>();
this.course = new ArrayList<>();
}
/**
* Add a boat to the race
*
* @param boat, the boat to add
*/
public void addBoat(Yacht boat) {
boats.add(boat);
}
/**
* Returns a list of boats in a random order
*
* @return a list of boats
*/
public Yacht[] getShuffledBoats() {
// Shuffle the list of boats
long seed = System.nanoTime();
Collections.shuffle(this.boats, new Random(seed));
return boats.toArray(new Yacht[boats.size()]);
}
/**
* Returns a list of boats in the order that they
* finished the race (position 0 is first place)
*
* @return a list of boats
*/
public Yacht[] getFinishedBoats() {
return this.finishingOrder.toArray(new Yacht[this.finishingOrder.size()]);
}
/**
* Returns a list of boats in the race
*
* @return a list of the boats competing in the race
*/
public Yacht[] getBoats() {
return boats.toArray(new Yacht[boats.size()]);
}
/**
* Sets time scale
*
* @param timeScale
*/
public void setTimeScale(double timeScale) {
this.timeScale = timeScale;
}
/**
* Generate all events that will happen during the race.
*/
private void generateEvents() {
for (Yacht boat : this.boats) {
double totalDistance = 0;
int numberOfMarks = this.course.size();
for (int i = 0; i < numberOfMarks; i++) {
Double time = (totalDistance / boat.getVelocity() / timeScale);
// If there are singleMarks after this event
if (i < numberOfMarks - 1) {
Event event = new Event(time, boat, course.get(i), course.get(i + 1), i);
try {
events.get(boat).add(event);
} catch (NullPointerException e) {
events.put(boat, new ArrayList<>(Arrays.asList(event)));
}
totalDistance += event.getDistanceBetweenMarks();
//System.out.println(totalDistance);
//System.out.println(boat.getVelocity());
}
// There are no more marks after this event
else{
Event event = new Event(time, boat, course.get(i), i);
events.get(boat).add(event);
}
}
}
}
/**
* Starts a race and generates all events for the race.
*/
public void startRace() {
// record start time.
this.startTime = System.currentTimeMillis();
generateEvents();
}
/**
* Set the race course
* @param course a list of marks in the course
*/
public void addCourse(List<Mark> course) {
this.course = course;
}
/**
* Get a list of marks in the course
* @return
*/
public List<Mark> getCourse() {
return course;
}
/**
* Get a map of the events in the race
* @return
*/
public HashMap<Yacht, List> getEvents() {
return events;
}
/**
* Set a boat as finished
* @param boat The boat that has finished the race/home/cosc/student/wmu16
*/
public void setBoatFinished(Yacht boat){
this.finishingOrder.add(boat);
}
/**
* Set the race as finished
*/
public void setRaceFinished(){
this.raceFinished = true;
}
/**
* Return whether or not the race is finished
* @return true if the race is finished
*/
public boolean isRaceFinished(){
return this.raceFinished;
}
/**
* Set the race time
* @param raceTime the race time in seconds
*/
public void setRaceTime(int raceTime){
this.raceTime = raceTime;
}
/**
* Return the race time
* @return the race time in seconds
*/
public int getRaceTime(){
return this.raceTime;
}
/**
* Increment the race time by one second
*/
public void incrementRaceTime(){
this.raceTime += this.timeScale;
}
}
@@ -1,87 +0,0 @@
package seng302.models;
import javafx.geometry.Point2D;
import javafx.scene.Group;
/**
* RaceObject defines the behaviour that animated objects whose position is updated from a yacht race data stream must
* adhere to.
*/
public abstract class RaceObject extends Group {
//Time between sections of race
protected static double expectedUpdateInterval = 200;
protected double rotationalGoal;
protected double currentRotation;
protected double rotationalVelocity;
protected double pixelVelocityX;
protected double pixelVelocityY;
public Point2D getPosition () {
return new Point2D(super.getLayoutX(), getLayoutY());
}
public static double getExpectedUpdateInterval() {
return expectedUpdateInterval;
}
/**
*
*/
public static void setExpectedUpdateInterval(double expectedUpdateInterval) {
RaceObject.expectedUpdateInterval = expectedUpdateInterval;
}
/**
* Calculates the rotational velocity required to reach the rotationalGoal from the currentRotation.
*/
protected void calculateRotationalVelocity () {
if (Math.abs(rotationalGoal - currentRotation) > 180) {
if (rotationalGoal - currentRotation >= 0) {
this.rotationalVelocity = ((rotationalGoal - currentRotation) - 360) / expectedUpdateInterval;
} else {
this.rotationalVelocity = (360 + (rotationalGoal - currentRotation)) / expectedUpdateInterval;
}
} else {
this.rotationalVelocity = (rotationalGoal - currentRotation) / expectedUpdateInterval;
}
//Sometimes the rotation is too large to be realistic. In that case just do it instantly.
if (Math.abs(rotationalVelocity) > 1) {
rotationalVelocity = 0;
rotateTo(rotationalGoal);
}
}
/**
* Sets the destination of everything within the RaceObject that has an ID in the array raceIds. The destination is
* set to the co-ordinates (x, y) with the given rotation.
* @param x X co-ordinate to move the graphics to.
* @param y Y co-ordinate to move the graphics to.
* @param rotation Rotation to move graphics to.
* @param raceIds RaceID of the object to move.
*/
public abstract void setDestination (double x, double y, double rotation, double groundSpeed, int... raceIds);
/**
* Sets the destination of everything within the RaceObject that has an ID in the array raceIds. The destination is
* set to the co-ordinates (x, y).
* @param x X co-ordinate to move the graphic to.
* @param y Y co-ordinate to move the graphic to.
* @param raceIds RaceID to the object to move.
*/
public abstract void setDestination (double x, double y, double groundSpeed, int... raceIds);
public abstract void updatePosition (long timeInterval);
public abstract void moveTo (double x, double y, double rotation);
public abstract void moveTo (double x, double y);
public abstract void moveGroupBy(double x, double y, double rotation);
public abstract void rotateTo (double rotation);
public abstract boolean hasRaceId (int... raceIds);
public abstract int[] getRaceIds ();
}
+50 -43
View File
@@ -1,30 +1,34 @@
package seng302.models;
import javafx.scene.CacheHint;
import javafx.scene.Group;
import javafx.scene.paint.Color;
import javafx.scene.shape.Arc;
import javafx.scene.shape.ArcType;
import javafx.scene.shape.StrokeLineCap;
import javafx.scene.transform.Rotate;
/**
* By default wake is a group containing 5 arcs. Each arc starts from the same point. Each arc is larger and more
* transparent than the last. On calling updatePositions() arcs rotate at velocities given by setRotationalVelocity().
* The larger and more transparent an arc is the longer the delay before it rotates at the latest velocity. It is
* assumed that rotationalVelocities() are set regularly as wakes do not stop rotating and an array of velocities needs
* to be populated for the class to work as expected.
* A group containing objects used to represent wakes onscreen. Contains functionality for their animation.
*/
class Wake extends Group {
private int numWakes = 5;
private double[] velocities = new double[13];
//The number of wakes
private int numWakes = 8;
//The total possible difference between the first wake and the last. Increasing/Decreasing this will make wakes fan out more/less.
private final double MAX_DIFF = 75;
//Increasing/decreasing this will alter the speed that wakes converge when the heading stop changing. Anything over about 1500 may cause oscillation.
private final int UNIFICATION_SPEED = 750;
private Arc[] arcs = new Arc[numWakes];
private double[] rotationalVelocities = new double[numWakes];
private double[] rotations = new double[numWakes];
private int[] velocityIndices = new int[numWakes];
private double sum = 0;
private static double max;
private double baseRad;
/**
* Create a wake at the given location.
*
* @param startingX x location where the tip of wake arcs will be.
* @param startingY y location where the tip of wake arcs will be.
*/
@@ -35,73 +39,76 @@ class Wake extends Group {
for (int i = 0; i < numWakes; i++) {
//Default triangle is -110 deg out of phase with a default wake and has angle of 40 deg.
arc = new Arc(0, 0, 0, 0, -110, 40);
//Opacity increases from 0.5 -> 0 evenly over the 5 wake arcs.
arc.setFill(new Color(0.18, 0.7, 1.0, 1.0 + -0.175 * i));
arc.setType(ArcType.ROUND);
arc.setCache(true);
arc.setCacheHint(CacheHint.SPEED);
arc.setType(ArcType.OPEN);
arc.setStroke(new Color(0.18, 0.7, 1.0, 1.0 + (-0.99 / numWakes * i)));
arc.setStrokeWidth(3.0);
arc.setStrokeLineCap(StrokeLineCap.ROUND);
arc.setFill(new Color(0.0, 0.0, 0.0, 0.0));
baseRad = (20 / numWakes);
arcs[i] = arc;
}
super.getChildren().addAll(arcs);
}
/**
* Sets the rotationalVelocity of each arc. Each arc is 3 velocities behind the next smallest arc. The smallest uses
* the latest given velocity.
* Sets the rotationalVelocity of each arc.
*
* @param rotationalVelocity The rotationalVelocity the wake should move at.
* @param rotationGoal Where the wake will rotate to if the wake is calculated to be on a straight section. This is
* used to prevent desynchronisation with the Boat polygon.
* @param velocity The real world velocity of the boat in m/s.
*/
void setRotationalVelocity (double rotationalVelocity, double rotationGoal, double velocity) {
sum -= Math.abs(velocities[(velocityIndices[0] + 10) % 13]);
sum += Math.abs(rotationalVelocity);
max = Math.max(max, rotationalVelocity);
if (sum < (max / 3))
rotate (rotationGoal); //In relatively straight segments the wake snaps to match the boats current position.
//This stops the wake from eventually becoming out of sync with the boat.
//This accounts for rogue rotations that are greater than what would be realistic. Value is kinda rough.
//Basically just for our internal mock.
if (Math.abs(rotationalVelocity) > 0.05) {
rotationalVelocity = 0;
rotate(rotationGoal);
}
//Update the index of the array of recent velocities that each wake uses. Each wake is 3 velocities behind the
//next smallest wake.
velocityIndices[0] = (13 + (velocityIndices[0] - 1) % 13) % 13;
velocities[velocityIndices[0]] = rotationalVelocity;
for (int i = 1; i < numWakes; i++)
velocityIndices[i] = (velocityIndices[0] + 3 * i) % 13;
void setRotationalVelocity(double rotationalVelocity, double velocity) {
rotationalVelocities[0] = rotationalVelocity;
for (int i = 1; i < numWakes; i++) {
double wakeSeparationRad = Math.toRadians(rotations[i - 1] - rotations[i]);
double shortestDistance = Math.atan2(
Math.sin(wakeSeparationRad),
Math.cos(wakeSeparationRad)
);
double distDeg = Math.toDegrees(shortestDistance);
//Scale wakes based on velocity.
double baseRad = 20;
double rad;
if (rotationalVelocities[i - 1] < 0.01 && rotationalVelocities[i - 1] > -0.01) {
rotationalVelocities[i] = distDeg / UNIFICATION_SPEED * Math.log(Math.abs(distDeg) + 1) / Math.log(MAX_DIFF / numWakes);
} else {
if (distDeg < (MAX_DIFF / numWakes))
rotationalVelocities[i] = rotationalVelocities[i - 1] * Math.log(Math.abs(distDeg) + 1) / Math.log(MAX_DIFF / numWakes);
else
rotationalVelocities[i] = rotationalVelocities[i - 1];
}
}
double rad = baseRad + velocity;
for (Arc arc : arcs) {
rad = baseRad + velocity;
arc.setRadiusX(rad);
arc.setRadiusY(rad);
baseRad += 5 + (velocity / 2);
rad += (20 / numWakes) + (velocity / 2);
}
}
/**
* Arcs rotate based on the distance they would have travelled over the supplied time interval.
*
* @param timeInterval the time interval, in microseconds, that the wake should move.
*/
void updatePosition(long timeInterval) {
for (int i = 0; i < numWakes; i++) {
rotations[i] = rotations[i] + velocities[velocityIndices[i]] * timeInterval;
rotations[i] = rotations[i] + rotationalVelocities[i] * timeInterval;
arcs[i].getTransforms().setAll(new Rotate(rotations[i]));
}
}
/**
* Rotate all wakes to the given rotation.
*
* @param rotation the from north angle in degrees to rotate to.
*/
void rotate(double rotation) {
for (int i = 0; i < arcs.length; i++) {
rotations[i] = rotation;
rotationalVelocities[i] = 0;
arcs[i].getTransforms().setAll(new Rotate(rotation));
}
}
}
+1 -1
View File
@@ -151,7 +151,7 @@ public class Yacht {
this.colour = colour;
}
public double getVelocity() {
public Double getVelocity() {
return velocity;
}
+20 -138
View File
@@ -1,13 +1,11 @@
package seng302.models.mark;
import javafx.geometry.Point2D;
import javafx.scene.CacheHint;
import javafx.scene.Node;
import javafx.scene.Group;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Line;
import javafx.scene.transform.Rotate;
import seng302.models.RaceObject;
import java.util.ArrayList;
import java.util.List;
@@ -15,7 +13,7 @@ import java.util.List;
/**
* Created by CJIRWIN on 26/04/2017.
*/
public class MarkGroup extends RaceObject {
public class MarkGroup extends Group {
private static int MARK_RADIUS = 5;
private static int LINE_THICKNESS = 2;
@@ -24,14 +22,8 @@ public class MarkGroup extends RaceObject {
private List<Mark> marks = new ArrayList<>();
private Mark mainMark;
private double[] nodePixelVelocitiesX;
private double[] nodePixelVelocitiesY;
private Point2D[] nodeDestinations;
public MarkGroup (Mark mark, Point2D... points) {
nodePixelVelocitiesX = new double[points.length];
nodePixelVelocitiesY = new double[points.length];
nodeDestinations = new Point2D[points.length];
marks.add(mark);
mainMark = mark;
Color color = Color.BLACK;
@@ -48,25 +40,14 @@ public class MarkGroup extends RaceObject {
MARK_RADIUS,
color
);
nodeDestinations = new Point2D[]{
new Point2D(markCircle.getCenterX(), markCircle.getCenterY()
)
};
super.getChildren().add(markCircle);
} else {
marks.add(((GateMark) mark).getSingleMark1());
marks.add(((GateMark) mark).getSingleMark2());
nodePixelVelocitiesX = new double[]{0d,0d};
nodePixelVelocitiesY = new double[]{0d,0d};
nodeDestinations = new Point2D[2];
markCircle = new Circle(
points[0].getX(),
points[0].getY(),
MARK_RADIUS,
color
);
nodeDestinations[0] = new Point2D(markCircle.getCenterX(), markCircle.getCenterY());
super.getChildren().add(markCircle);
markCircle = new Circle(
@@ -75,7 +56,6 @@ public class MarkGroup extends RaceObject {
MARK_RADIUS,
color
);
nodeDestinations[1] = new Point2D(markCircle.getCenterX(), markCircle.getCenterY());
super.getChildren().add(markCircle);
Line line = new Line(
points[0].getX(),
@@ -92,117 +72,27 @@ public class MarkGroup extends RaceObject {
}
}
public void setDestination (double x, double y, double rotation, double groundSpeed, int... raceIds) {
setDestination(x, y, 0, raceIds);
this.rotationalGoal = rotation;
calculateRotationalVelocity();
}
public void setDestination (double x, double y, double groundSpeed, int... raceIds) {
for (int i = 0; i < marks.size(); i++)
for (int id : raceIds)
if (id == marks.get(i).getId())
setDestinationChild(x, y, 0, Math.max(0, i-1));
}
private void setDestinationChild (double x, double y, double speed, int childIndex) {
//double relativeX = x - super.getLayoutX();
//double relativeY = y - super.getLayoutY();
Circle markCircle = (Circle) super.getChildren().get(childIndex);
this.nodeDestinations[childIndex] = new Point2D(x, y);
//if (Math.abs(relativeX - markCircle.getCenterX()) > 30 && Math.abs(relativeY - markCircle.getCenterY()) > 30) {
this.nodePixelVelocitiesX[childIndex] = (x - markCircle.getCenterX()) / expectedUpdateInterval;
this.nodePixelVelocitiesY[childIndex] = (y - markCircle.getCenterY()) / expectedUpdateInterval;
//}
}
public void rotateTo (double rotation) {
if (mainMark.getMarkType() != MarkType.SINGLE_MARK) {
Line line = (Line) super.getChildren().get(2);
double xCenter = Math.abs(line.getEndX() - line.getStartX());
double yCenter = Math.abs(line.getEndY() - line.getStartY());
super.getTransforms().setAll(new Rotate(rotation, xCenter, yCenter));
}
}
public void updatePosition (long timeInterval) {
public void moveMarkTo (double x, double y, int raceId)
{
if (mainMark.getMarkType() == MarkType.SINGLE_MARK) {
Circle markCircle = (Circle) super.getChildren().get(0);
if (nodePixelVelocitiesX[0] > 0 && markCircle.getCenterX() > nodeDestinations[0].getX() ||
nodePixelVelocitiesX[0] < 0 && markCircle.getCenterX() < nodeDestinations[0].getY())
nodePixelVelocitiesX[0] = 0;
else if (nodePixelVelocitiesX[0] != 0)
markCircle.setCenterX(markCircle.getCenterX() + nodePixelVelocitiesX[0] * timeInterval);
if (nodePixelVelocitiesY[0] > 0 && markCircle.getCenterY() > nodeDestinations[0].getY() ||
nodePixelVelocitiesY[0] < 0 && markCircle.getCenterY() < nodeDestinations[0].getY())
nodePixelVelocitiesY[0] = 0;
else if (nodePixelVelocitiesY[0] != 0)
markCircle.setCenterY(markCircle.getCenterY() + nodePixelVelocitiesY[0] * timeInterval);
if (mainMark.getMarkType() != MarkType.SINGLE_MARK) {
Line line = (Line) super.getChildren().get(2);
line.setStartX(markCircle.getCenterX());
line.setStartY(markCircle.getCenterY());
markCircle = (Circle) super.getChildren().get(1);
if (nodePixelVelocitiesX[1] > 0 && markCircle.getCenterX() >= nodeDestinations[1].getX() ||
nodePixelVelocitiesX[1] < 0 && markCircle.getCenterX() <= nodeDestinations[1].getX())
nodePixelVelocitiesX[1] = 0;
else if (nodePixelVelocitiesX[1] != 0)
markCircle.setCenterX(markCircle.getCenterX() + nodePixelVelocitiesX[1] * timeInterval);
if (nodePixelVelocitiesY[1] > 0 && markCircle.getCenterY() > nodeDestinations[1].getY() ||
nodePixelVelocitiesY[1] < 0 && markCircle.getCenterY() < nodeDestinations[1].getY())
nodePixelVelocitiesY[1] = 0;
else if (nodePixelVelocitiesY[1] != 0)
markCircle.setCenterY(markCircle.getCenterY() + nodePixelVelocitiesY[1] * timeInterval);
line.setEndX(markCircle.getCenterX());
line.setEndY(markCircle.getCenterY());
}
}
public void moveGroupBy (double x, double y, double rotation) {
if (mainMark.getMarkType() != MarkType.SINGLE_MARK) {
Line line = (Line) super.getChildren().get(2);
for (int childIndex = 0; childIndex < 2; childIndex++){
Circle mark = (Circle) super.getChildren().get(childIndex);
mark.setCenterY(mark.getCenterY() + y);
mark.setCenterX(mark.getCenterX() + x);
}
line.setStartX(line.getStartX() + x);
line.setStartY(line.getStartY() + y);
line.setEndX(line.getEndX() + x);
line.setEndY(line.getEndY() + y);
markCircle.setCenterX(x);
markCircle.setCenterY(y);
} else {
Circle mark = (Circle) super.getChildren().get(0);
mark.setCenterY(mark.getCenterY() + y);
mark.setCenterX(mark.getCenterX() + x);
Circle markCircle1 = (Circle) super.getChildren().get(0);
Circle markCircle2 = (Circle) super.getChildren().get(1);
Line connectingLine = (Line) super.getChildren().get(2);
if (marks.get(1).getId() == raceId) {
markCircle1.setCenterX(x);
markCircle1.setCenterY(y);
connectingLine.setStartX(markCircle1.getCenterX());
connectingLine.setStartY(markCircle1.getCenterY());
} else if (marks.get(2).getId() == raceId) {
markCircle2.setCenterX(x);
markCircle2.setCenterY(y);
connectingLine.setEndX(markCircle2.getCenterX());
connectingLine.setEndY(markCircle2.getCenterY());
}
rotateTo(currentRotation + rotation);
}
public void moveTo (double x, double y, double rotation) {
moveTo(x, y);
rotateTo(rotation);
}
public void moveTo (double x, double y) {
Circle markCircle = (Circle) super.getChildren().get(0);
markCircle.setCenterX(x);
markCircle.setCenterY(y);
if (mainMark.getMarkType() != MarkType.SINGLE_MARK) {
markCircle = (Circle) super.getChildren().get(1);
markCircle.setCenterX(x);
markCircle.setCenterY(y);
Line line = (Line) super.getChildren().get(2);
line.setStartX(x);
line.setStartY(y);
line.setEndX(x);
line.setEndY(y);
}
}
@@ -214,14 +104,6 @@ public class MarkGroup extends RaceObject {
return false;
}
public static int getMarkRadius() {
return MARK_RADIUS;
}
public static void setMarkRadius(int markRadius) {
MARK_RADIUS = markRadius;
}
public int[] getRaceIds () {
int[] idArray = new int[marks.size()];
int i = 0;
@@ -1,78 +0,0 @@
package seng302.models.parsers;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import java.util.DoubleSummaryStatistics;
public class ConfigParser extends FileParser {
private Document doc;
public ConfigParser(String path) {
super(path);
this.doc = this.parseFile();
}
/**
* Gets wind direction from config file.
*
* @return a double type degree, or 0 if no value or invalid value is found
*/
public double getWindDirection() {
return getDoubleByTagName("wind-direction", 0.0);
}
/**
* Gets a non negative time scale for the race
*
* @return a double type scale, or 0 if no scale or invalid scale is found
*/
public double getTimeScale() {
return getDoubleByTagName("time-scale", 1.0);
}
/**
* Gets a double type number by given tag name found in xml file
*
* @param tagName a string of tag name
* @param defaultVal value returned if no value or invalid value is found
* @return value found
*/
public double getDoubleByTagName(String tagName, double defaultVal) {
double val = defaultVal;
try {
Node node = this.doc.getElementsByTagName(tagName).item(0);
if (node.getNodeType() == Node.ELEMENT_NODE) {
Element element = (Element) node;
val = Double.valueOf(element.getTextContent());
}
} catch (Exception e) {
} finally {
return val;
}
}
/**
* Gets a string by given tag name found in xml file
*
* @param tagName a string of tag name
* @param defaultVal a string returned if no value or invalid value is found
* @return string found
*/
public String getStringByTagName(String tagName, String defaultVal) {
String string = defaultVal;
try {
Node node = this.doc.getElementsByTagName(tagName).item(0);
if (node.getNodeType() == Node.ELEMENT_NODE) {
Element element = (Element) node;
string = element.getTextContent();
}
} catch (Exception e) {
} finally {
return string;
}
}
}
@@ -1,145 +0,0 @@
package seng302.models.parsers;
import org.w3c.dom.*;
import seng302.models.mark.*;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.NoSuchElementException;
/**
* parse a course xml file
* Created by Haoming Yin (hyi25) on 16/3/2017
*/
public class CourseParser extends FileParser {
private Document doc;
private HashMap<String, Mark> marks = new HashMap<>();
public CourseParser(String path) {
super(path);
this.doc = this.parseFile();
}
/**
* create a mark by given node
*
* @param node
* @return a mark, or null if fails to create a mark
*/
private SingleMark generateSingleMark(Node node) {
try {
if (node.getNodeType() == Node.ELEMENT_NODE) {
Element element = (Element) node;
String name = element.getElementsByTagName("name").item(0).getTextContent();
double lat = Double.valueOf(element.getElementsByTagName("latitude").item(0).getTextContent());
double lon = Double.valueOf(element.getElementsByTagName("longitude").item(0).getTextContent());
int id = Integer.valueOf(element.getElementsByTagName("id").item(0).getTextContent());
SingleMark singleMark = new SingleMark(name, lat, lon, id);
return singleMark;
} else {
throw new NoSuchElementException("Cannot generate a mark by given node.");
}
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* generate an arrayList of gates
*
* @return an arrayList of gates, or null if no gate has been found.
*/
private void generateGateMarks() {
ArrayList<GateMark> gateMarks = new ArrayList<>();
try {
NodeList nodes = doc.getElementsByTagName("gate");
for (int i = 0; i < nodes.getLength(); i++) {
Node node = nodes.item(i);
if (node.getNodeType() == Node.ELEMENT_NODE) {
Element element = (Element) node;
String name = element.getElementsByTagName("name").item(0).getTextContent();
SingleMark mark1 = generateSingleMark(element.getElementsByTagName("mark").item(0));
SingleMark mark2 = generateSingleMark(element.getElementsByTagName("mark").item(1));
GateMark gateMark;
if (name.equals("Start") || name.equals("Finish"))
gateMark = new GateMark(name, MarkType.CLOSED_GATE, mark1, mark2, mark1.getLatitude(), mark1.getLongitude());
else
gateMark = new GateMark(name, MarkType.OPEN_GATE, mark1, mark2, mark1.getLatitude(), mark1.getLongitude());
marks.put(name, gateMark);
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* generate an arrayList of marks
*
* @return an arrayList of marks, or null if no gate has been found.
*/
private void generateSingleMarks() {
ArrayList<SingleMark> singleMarks = new ArrayList<>();
try {
// find the "marks" tag
Node node = doc.getElementsByTagName("marks").item(0);
// iterate all "marks"'s children
for (Node n = node.getFirstChild(); n != null; n = n.getNextSibling()) {
// if node's tag name is "mark"
if (n.getNodeType() == Node.ELEMENT_NODE) {
Element element = (Element) n;
if (element.getNodeName() == "mark") {
Mark mark = generateSingleMark(n);
marks.put(mark.getName(), mark);
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* return the order of all the marks along a course
*
* @return an arrayList of the names of ordered course marks
*/
private ArrayList<String> getOrder() {
ArrayList<String> markOrder = new ArrayList<>();
try {
Node orderNode = doc.getElementsByTagName("order").item(0);
for (Node node = orderNode.getFirstChild(); node != null; node = node.getNextSibling()) {
if (node.getNodeType() == Node.ELEMENT_NODE) {
Element element = (Element) node;
String name = element.getTextContent();
markOrder.add(name);
}
}
} catch (Exception e) {
e.printStackTrace();
}
return markOrder;
}
public ArrayList<Mark> getCourse() {
generateSingleMarks();
generateGateMarks();
ArrayList<Mark> course = new ArrayList<>();
try {
for (String mark : getOrder()) {
course.add(marks.get(mark));
}
} catch (Exception e) {
e.printStackTrace();
}
return course;
}
}
@@ -1,54 +0,0 @@
package seng302.models.parsers;
import org.w3c.dom.Document;
import org.xml.sax.InputSource;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.StringReader;
/**
* Created by Haoming Yin (hyi25) on 16/3/2017
*/
public abstract class FileParser {
private String filePath;
public FileParser() {}
public FileParser(String path) {
this.filePath = path;
}
protected Document parseFile() {
try {
InputStream is = getClass().getResourceAsStream(this.filePath);
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
Document doc = builder.parse(is);
// optional, in order to recover info from broken line.
doc.getDocumentElement().normalize();
return doc;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
protected Document parseFile(String xmlString) {
try {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
Document doc = builder.parse(new InputSource(new StringReader(xmlString)));
// optional, in order to recover info from broken line.
doc.getDocumentElement().normalize();
return doc;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
@@ -1,64 +0,0 @@
//package seng302.models.parsers;
//
//import org.w3c.dom.*;
//import seng302.models.Yacht;
//
//import java.util.ArrayList;
//import java.util.NoSuchElementException;
//
//public class TeamsParser extends FileParser {
//
// private Document doc;
//
// public TeamsParser(String path) {
// super(path);
// this.doc = this.parseFile();
// }
//
// /**
// * Create a boat instance by a given team node
// * @param node a boat node containing name, alias and velocity
// * @return an instance of Boat
// */
// private Yacht parseBoat(Node node) {
// try {
// if (node.getNodeType() == Node.ELEMENT_NODE) {
// Element element = (Element) node;
// String name = element.getElementsByTagName("name").item(0).getTextContent();
// String alias = element.getElementsByTagName("alias").item(0).getTextContent();
// double velocity = Double.valueOf(element.getElementsByTagName("velocity").item(0).getTextContent());
// int id = Integer.valueOf(element.getElementsByTagName("id").item(0).getTextContent());
// Yacht boat = new Yacht(name, velocity, alias, id);
// return boat;
// } else {
// throw new NoSuchElementException("Cannot generate a boat by given node");
// }
// } catch (Exception e) {
// e.printStackTrace();
// return null;
// }
// }
//
// /**
// * Create an arraylist of boats instance.
// * @return an arraylist of boats in teams file
// */
// public ArrayList<Yacht> getBoats() {
// ArrayList<Yacht> boats = new ArrayList<>();
//
// try {
// NodeList nodes = this.doc.getElementsByTagName("team");
// for (int i = 0; i < nodes.getLength(); i++) {
// Node node = nodes.item(i);
// boats.add(parseBoat(node));
// }
// return boats;
// } catch (Exception e) {
// e.printStackTrace();
// return null;
// }
// }
//
//
//}
//
@@ -1,12 +1,12 @@
package seng302.models.parsers;
package seng302.models.stream;
import org.w3c.dom.Document;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import seng302.models.Yacht;
import seng302.models.parsers.packets.BoatPositionPacket;
import seng302.models.parsers.packets.StreamPacket;
import seng302.models.stream.packets.BoatPositionPacket;
import seng302.models.stream.packets.StreamPacket;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
@@ -17,7 +17,9 @@ import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentSkipListMap;
import java.util.concurrent.PriorityBlockingQueue;
import seng302.models.stream.XMLParser;
/**
* The purpose of this class is to take in the stream of divided packets so they can be read
@@ -36,8 +38,8 @@ public class StreamParser extends Thread{
private static boolean raceFinished = false;
private static boolean streamStatus = false;
private static long timeSinceStart = -1;
private static Map<Integer, Yacht> boats = new HashMap<>();
private static Map<Long, Yacht> boatsPos = new TreeMap<>();
private static Map<Integer, Yacht> boats = new ConcurrentHashMap<>();
private static Map<Long, Yacht> boatsPos = new ConcurrentSkipListMap<>();
private static double windDirection = 0;
private static Long currentTimeLong;
private static String currentTimeString;
@@ -67,24 +69,10 @@ public class StreamParser extends Thread{
Thread.sleep(1);
}
while (appRunning){
StreamPacket packet = StreamReceiver.packetBuffer.peek();
//this code adds a delay to reading from the packetBuffer so
//out of order packets have time to order themselves in the queue
int delayTime = 1000;
int loopTime = delayTime * 10;
long transitTime = (System.currentTimeMillis()%loopTime - packet.getTimeStamp()%loopTime);
if (transitTime < 0){
transitTime = loopTime + transitTime;
}
if (transitTime < delayTime) {
long sleepTime = delayTime - (transitTime);
Thread.sleep(sleepTime);
}
packet = StreamReceiver.packetBuffer.take();
StreamPacket packet = StreamReceiver.packetBuffer.take();
parsePacket(packet);
Thread.sleep(1);
while (StreamReceiver.packetBuffer.peek() == null) {
Thread.sleep(1);
}
}
} catch (Exception e){
@@ -223,7 +211,6 @@ public class StreamParser extends Thread{
raceFinished = false;
System.out.println("[CLIENT] Race has started");
}
//System.out.println("Time since start: " + -1 * timeTillStart + " Seconds");
timeSinceStart = timeTillStart;
}
@@ -232,11 +219,10 @@ public class StreamParser extends Thread{
int noBoats = payload[22];
int raceType = payload[23];
// ArrayList<String> boatStatuses = new ArrayList<>();
boatsPos = new TreeMap<>();
for (int i = 0; i < noBoats; i++){
Long boatStatusSourceID = bytesToLong(Arrays.copyOfRange(payload,24 + (i * 20),28+ (i * 20)));
Yacht boat = boats.get((int)(long) boatStatusSourceID);
long boatStatusSourceID = bytesToLong(Arrays.copyOfRange(payload,24 + (i * 20),28+ (i * 20)));
Yacht boat = boats.get((int) boatStatusSourceID);
boat.setBoatStatus((int)payload[28 + (i * 20)]);
boat.setLegNumber((int)payload[29 + (i * 20)]);
boat.setPenaltiesAwarded((int)payload[30 + (i * 20)]);
@@ -364,7 +350,6 @@ public class StreamParser extends Thread{
long subjectId = bytesToLong(Arrays.copyOfRange(payload,9,13));
long incidentId = bytesToLong(Arrays.copyOfRange(payload,13,17));
int eventId = payload[17];
// System.out.println("eventId = " + eventId);
}
/**
@@ -402,19 +387,18 @@ public class StreamParser extends Thread{
double groundSpeed = bytesToLong(Arrays.copyOfRange(payload,38,40))/1000.0;
//type 1 is a racing yacht and type 3 is a mark, needed for updating positions of the mark and boat
if (deviceType == 1 || deviceType == 3){
if (deviceType == 1){
BoatPositionPacket boatPacket = new BoatPositionPacket(boatId, timeValid, lat, lon, heading, groundSpeed);
//add a new priority que to the boatPositions HashMap
if (!boatPositions.containsKey(boatId)){
boatPositions.put(boatId, new PriorityBlockingQueue<BoatPositionPacket>(256, new Comparator<BoatPositionPacket>() {
boatPositions.put(boatId, new PriorityBlockingQueue<>(256, new Comparator<BoatPositionPacket>() {
@Override
public int compare(BoatPositionPacket p1, BoatPositionPacket p2) {
return (int) (p1.getTimeValid() - p2.getTimeValid());
}
}));
}
//Adding the boatPacket to the priority que
boatPositions.get(boatId).put(boatPacket);
}
}
@@ -1,13 +1,11 @@
package seng302.models.parsers;
package seng302.models.stream;
import seng302.models.parsers.packets.StreamPacket;
import seng302.models.stream.packets.StreamPacket;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.Socket;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
import java.util.concurrent.PriorityBlockingQueue;
import java.util.zip.CRC32;
@@ -1,14 +1,14 @@
package seng302.models.parsers;
package seng302.models.stream;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import seng302.models.Yacht;
import seng302.models.mark.MarkType;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
@@ -301,6 +301,7 @@ public class XMLParser {
public class CompoundMark {
private Integer markID;
private String cMarkName;
private MarkType markType;
private ArrayList<Mark> marks;
CompoundMark(Node compoundMark) {
@@ -308,6 +309,12 @@ public class XMLParser {
this.markID = getNodeAttributeInt(compoundMark, "CompoundMarkID");
this.cMarkName = getNodeAttributeString(compoundMark, "Name");
NodeList childMarks = compoundMark.getChildNodes();
if (childMarks.getLength() > 1){
markType = MarkType.OPEN_GATE;
} else {
markType = MarkType.SINGLE_MARK;
}
for (int i = 0; i < childMarks.getLength(); i++) {
Node markNode = childMarks.item(i);
if (markNode.getNodeName().equals("Mark")) {
@@ -319,6 +326,7 @@ public class XMLParser {
public Integer getMarkID() { return markID; }
public String getcMarkName() { return cMarkName; }
public MarkType getMarkType() { return markType; }
public ArrayList<Mark> getMarks() { return marks; }
public class Mark {
@@ -1,4 +1,4 @@
package seng302.models.parsers.packets;
package seng302.models.stream.packets;
public class BoatPositionPacket {
private long boatId;
@@ -1,4 +1,4 @@
package seng302.models.parsers.packets;
package seng302.models.stream.packets;
/**
* Created by Kusal on 4/24/2017.
@@ -48,6 +48,4 @@ public enum PacketType {
}
return OTHER;
}
}
@@ -1,4 +1,4 @@
package seng302.models.parsers.packets;
package seng302.models.stream.packets;
/**
* Created by kre39 on 23/04/17.
@@ -1,10 +1,7 @@
package seng302.server.messages;
import java.io.DataOutputStream;
import java.io.IOException;
import java.nio.channels.Channels;
import java.nio.channels.SocketChannel;
import java.nio.channels.WritableByteChannel;
public class BoatLocationMessage extends Message {
private final int MESSAGE_SIZE = 56;
@@ -44,7 +41,7 @@ public class BoatLocationMessage extends Message {
public BoatLocationMessage(int sourceId, int sequenceNum, double latitude, double longitude, double heading, long boatSpeed){
boatSpeed /= 10;
messageVersionNumber = 1;
time = System.currentTimeMillis() / 1000L;
time = System.currentTimeMillis();
this.sourceId = sourceId;
this.sequenceNum = sequenceNum;
this.deviceType = DeviceType.RACING_YACHT;
-12
View File
@@ -1,21 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import java.lang.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<?import javafx.scene.shape.*?>
<?import javafx.scene.text.*?>
<?import javafx.scene.control.CheckBox?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.layout.ColumnConstraints?>
<?import javafx.scene.layout.GridPane?>
<?import javafx.scene.layout.Pane?>
<?import javafx.scene.layout.RowConstraints?>
<?import javafx.scene.layout.VBox?>
<?import javafx.scene.shape.Circle?>
<?import javafx.scene.text.Font?>
<?import javafx.scene.text.Text?>
<GridPane prefHeight="1080.0" prefWidth="1920.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="seng302.controllers.RaceViewController">
<columnConstraints>
+1 -4
View File
@@ -8,14 +8,11 @@ import seng302.models.Colors;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
/**
* Created by ryan_ on 16/03/2017.
*/
public class ColorsTest {
@Test
public void testNextColor() {
Color expectedColors[] = {Color.RED, Color.ORANGE, Color.YELLOW, Color.GREEN, Color.BLUE, Color.PURPLE};
Color expectedColors[] = {Color.RED, Color.PERU, Color.SEAGREEN, Color.GREEN, Color.BLUE, Color.PURPLE};
for (int i = 0; i<6; i++)
{
Assert.assertEquals(expectedColors[i], Colors.getColor());
-41
View File
@@ -1,41 +0,0 @@
package seng302;
import org.junit.Test;
import seng302.models.Race;
import seng302.models.Yacht;
import java.lang.reflect.Array;
import static org.junit.Assert.assertEquals;
/**
* Unit test for the Race class.
*/
public class RaceTest {
/**
* Test that all boats were added to the race
*/
@Test
public void testAddingBoatsToRace() {
Yacht boat1 = new Yacht("Team 1");
Yacht boat2 = new Yacht("Team 2");
Race race = new Race();
race.addBoat(boat1);
race.addBoat(boat2);
assertEquals(Array.getLength(race.getBoats()), 2);
}
@Test
public void testGetShuffledBoats(){
Yacht boat1 = new Yacht("Team 1");
Yacht boat2 = new Yacht("Team 2");
Race race = new Race();
race.addBoat(boat1);
race.addBoat(boat2);
assertEquals(Array.getLength(race.getShuffledBoats()), 2);
}
}
@@ -1,42 +0,0 @@
package seng302.models.parsers;
import org.junit.Before;
import org.junit.Test;
import static org.junit.Assert.*;
/**
* Created by Haoming on 23/03/17.
*/
public class ConfigParserTest {
private ConfigParser cp;
@Before
public void initializeParser() throws Exception {
cp = new ConfigParser("/config/config.xml");
}
@Test
public void getWindDirection() throws Exception {
assertEquals(135, cp.getWindDirection(), 1e-10);
}
@Test
public void getTimeScale() throws Exception {
assertEquals(10.0, cp.getTimeScale(), 1e-10);
}
@Test
public void getDoubleByTagName() throws Exception {
assertEquals(6, cp.getDoubleByTagName("race-size", 0), 1e-10);
assertEquals(100, cp.getDoubleByTagName("noTag", 100), 1e-10);
}
@Test
public void getStringByTagName() throws Exception {
assertEquals("AC35", cp.getStringByTagName("race-name", "11"));
assertEquals("oops", cp.getStringByTagName("noTag", "oops"));
}
}
@@ -1,59 +0,0 @@
package seng302.models.parsers;
import org.junit.Before;
import org.junit.Test;
import seng302.models.mark.*;
import java.util.ArrayList;
import static org.junit.Assert.*;
/**
* To test if course parser works as expected.
* Created by Haoming on 17/03/17.
*/
public class CourseParserTest {
private CourseParser cp;
@Before
public void initializeParser() throws Exception {
cp = new CourseParser("/config/course.xml");
}
@Test
public void getGates() throws Exception {
ArrayList<Mark> course = cp.getCourse();
GateMark gateMark1 = (GateMark) course.get(0);
assertEquals(57.670633, gateMark1.getSingleMark2().getLatitude(), 0.00000001);
assertEquals(11.8281330, gateMark1.getSingleMark2().getLongitude(), 0.00000001);
GateMark gateMark2 = (GateMark) course.get(5);
assertEquals("Finish1", gateMark2.getSingleMark1().getName());
assertEquals("Finish2", gateMark2.getSingleMark2().getName());
assertEquals(57.671824, gateMark2.getSingleMark2().getLatitude(), 0.00000001);
assertEquals(11.844795, gateMark2.getSingleMark2().getLongitude(), 0.00000001);
}
@Test
public void getMarks() throws Exception {
ArrayList<Mark> course = cp.getCourse();
assertEquals("Mid Mark", course.get(1).getName());
}
@Test
public void getOrder() {
ArrayList<Mark> course = cp.getCourse();
assertEquals(6, course.size());
assertEquals("Start", course.get(0).getName());
assertEquals("Mid Mark", course.get(1).getName());
assertEquals("Leeward Gate", course.get(2).getName());
assertEquals("Windward Gate", course.get(3).getName());
assertEquals("Leeward Gate", course.get(4).getName());
assertEquals("Finish", course.get(5).getName());
}
}
@@ -1,4 +1,4 @@
package seng302.models.parsers;
package seng302.models.stream;
import org.junit.Before;
import org.junit.Test;
@@ -8,7 +8,7 @@ import java.lang.reflect.Method;
import java.net.Socket;
import java.util.Comparator;
import java.util.concurrent.PriorityBlockingQueue;
import seng302.models.parsers.packets.StreamPacket;
import seng302.models.stream.packets.StreamPacket;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;