The following schema fragment specifies the expected content contained within this class.
+ *
+ *
The following schema fragment specifies the expected content contained within this class.
+ *
+ *
boat;
+
+ /**
+ * Gets the value of the boat property.
+ *
+ *
+ * This accessor method returns a reference to the live list,
+ * not a snapshot. Therefore any modification you make to the
+ * returned list will be present inside the JAXB object.
+ * This is why there is not a set method for the boat property.
+ *
+ *
+ * For example, to add a new item, do as follows:
+ *
+ * getBoat().add(newItem);
+ *
+ *
+ *
+ *
+ * Objects of the following type(s) are allowed in the list
+ * {@link BoatConfig.Boats.Boat }
+ *
+ *
+ */
+ public List getBoat() {
+ if (boat == null) {
+ boat = new ArrayList();
+ }
+ return this.boat;
+ }
+
+
+ /**
+ * Java class for anonymous complex type.
+ *
+ *
The following schema fragment specifies the expected content contained within this class.
+ *
+ *
+ * <complexType>
+ * <complexContent>
+ * <restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
+ * <sequence>
+ * <element name="GPSposition">
+ * <complexType>
+ * <complexContent>
+ * <restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
+ * <attribute name="X" type="{http://www.w3.org/2001/XMLSchema}double" />
+ * <attribute name="Y" use="required" type="{http://www.w3.org/2001/XMLSchema}double" />
+ * <attribute name="Z" use="required" type="{http://www.w3.org/2001/XMLSchema}double" />
+ * </restriction>
+ * </complexContent>
+ * </complexType>
+ * </element>
+ * </sequence>
+ * <attribute name="Type" use="required" type="{http://www.w3.org/2001/XMLSchema}string" />
+ * <attribute name="BoatName" use="required" type="{http://www.w3.org/2001/XMLSchema}string" />
+ * <attribute name="SourceID" use="required" type="{http://www.w3.org/2001/XMLSchema}int" />
+ * <attribute name="HullNum" type="{http://www.w3.org/2001/XMLSchema}string" />
+ * <attribute name="ShortName" type="{http://www.w3.org/2001/XMLSchema}string" />
+ * <attribute name="ShapeID" type="{http://www.w3.org/2001/XMLSchema}int" />
+ * <attribute name="StoweName" type="{http://www.w3.org/2001/XMLSchema}string" />
+ * </restriction>
+ * </complexContent>
+ * </complexType>
+ *
+ *
+ *
+ */
+ @XmlAccessorType(XmlAccessType.FIELD)
+ @XmlType(name = "", propOrder = {
+ "gpSposition"
+ })
+ public static class Boat {
+
+ @XmlElement(name = "GPSposition", required = true)
+ protected BoatConfig.Boats.Boat.GPSposition gpSposition;
+ @XmlAttribute(name = "Type", required = true)
+ protected String type;
+ @XmlAttribute(name = "BoatName", required = true)
+ protected String boatName;
+ @XmlAttribute(name = "SourceID", required = true)
+ protected int sourceID;
+ @XmlAttribute(name = "HullNum")
+ protected String hullNum;
+ @XmlAttribute(name = "ShortName")
+ protected String shortName;
+ @XmlAttribute(name = "ShapeID")
+ protected Integer shapeID;
+ @XmlAttribute(name = "StoweName")
+ protected String stoweName;
+
+ /**
+ * Gets the value of the gpSposition property.
+ *
+ * @return
+ * possible object is
+ * {@link BoatConfig.Boats.Boat.GPSposition }
+ *
+ */
+ public BoatConfig.Boats.Boat.GPSposition getGPSposition() {
+ return gpSposition;
+ }
+
+ /**
+ * Sets the value of the gpSposition property.
+ *
+ * @param value
+ * allowed object is
+ * {@link BoatConfig.Boats.Boat.GPSposition }
+ *
+ */
+ public void setGPSposition(BoatConfig.Boats.Boat.GPSposition value) {
+ this.gpSposition = value;
+ }
+
+ /**
+ * Gets the value of the type property.
+ *
+ * @return
+ * possible object is
+ * {@link String }
+ *
+ */
+ public String getType() {
+ return type;
+ }
+
+ /**
+ * Sets the value of the type property.
+ *
+ * @param value
+ * allowed object is
+ * {@link String }
+ *
+ */
+ public void setType(String value) {
+ this.type = value;
+ }
+
+ /**
+ * Gets the value of the boatName property.
+ *
+ * @return
+ * possible object is
+ * {@link String }
+ *
+ */
+ public String getBoatName() {
+ return boatName;
+ }
+
+ /**
+ * Sets the value of the boatName property.
+ *
+ * @param value
+ * allowed object is
+ * {@link String }
+ *
+ */
+ public void setBoatName(String value) {
+ this.boatName = value;
+ }
+
+ /**
+ * Gets the value of the sourceID property.
+ *
+ */
+ public int getSourceID() {
+ return sourceID;
+ }
+
+ /**
+ * Sets the value of the sourceID property.
+ *
+ */
+ public void setSourceID(int value) {
+ this.sourceID = value;
+ }
+
+ /**
+ * Gets the value of the hullNum property.
+ *
+ * @return
+ * possible object is
+ * {@link String }
+ *
+ */
+ public String getHullNum() {
+ return hullNum;
+ }
+
+ /**
+ * Sets the value of the hullNum property.
+ *
+ * @param value
+ * allowed object is
+ * {@link String }
+ *
+ */
+ public void setHullNum(String value) {
+ this.hullNum = value;
+ }
+
+ /**
+ * Gets the value of the shortName property.
+ *
+ * @return
+ * possible object is
+ * {@link String }
+ *
+ */
+ public String getShortName() {
+ return shortName;
+ }
+
+ /**
+ * Sets the value of the shortName property.
+ *
+ * @param value
+ * allowed object is
+ * {@link String }
+ *
+ */
+ public void setShortName(String value) {
+ this.shortName = value;
+ }
+
+ /**
+ * Gets the value of the shapeID property.
+ *
+ * @return
+ * possible object is
+ * {@link Integer }
+ *
+ */
+ public Integer getShapeID() {
+ return shapeID;
+ }
+
+ /**
+ * Sets the value of the shapeID property.
+ *
+ * @param value
+ * allowed object is
+ * {@link Integer }
+ *
+ */
+ public void setShapeID(Integer value) {
+ this.shapeID = value;
+ }
+
+ /**
+ * Gets the value of the stoweName property.
+ *
+ * @return
+ * possible object is
+ * {@link String }
+ *
+ */
+ public String getStoweName() {
+ return stoweName;
+ }
+
+ /**
+ * Sets the value of the stoweName property.
+ *
+ * @param value
+ * allowed object is
+ * {@link String }
+ *
+ */
+ public void setStoweName(String value) {
+ this.stoweName = value;
+ }
+
+
+ /**
+ * Java class for anonymous complex type.
+ *
+ *
The following schema fragment specifies the expected content contained within this class.
+ *
+ *
+ * <complexType>
+ * <complexContent>
+ * <restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
+ * <attribute name="X" type="{http://www.w3.org/2001/XMLSchema}double" />
+ * <attribute name="Y" use="required" type="{http://www.w3.org/2001/XMLSchema}double" />
+ * <attribute name="Z" use="required" type="{http://www.w3.org/2001/XMLSchema}double" />
+ * </restriction>
+ * </complexContent>
+ * </complexType>
+ *
+ *
+ *
+ */
+ @XmlAccessorType(XmlAccessType.FIELD)
+ @XmlType(name = "")
+ public static class GPSposition {
+
+ @XmlAttribute(name = "X", required = true)
+ protected Double x;
+ @XmlAttribute(name = "Y", required = true)
+ protected double y;
+ @XmlAttribute(name = "Z", required = true)
+ protected double z;
+
+ /**
+ * Gets the value of the x property.
+ *
+ * @return
+ * possible object is
+ * {@link Double }
+ *
+ */
+ public Double getX() {
+ return x;
+ }
+
+ /**
+ * Sets the value of the x property.
+ *
+ * @param value
+ * allowed object is
+ * {@link Double }
+ *
+ */
+ public void setX(Double value) {
+ this.x = value;
+ }
+
+ /**
+ * Gets the value of the y property.
+ *
+ */
+ public double getY() {
+ return y;
+ }
+
+ /**
+ * Sets the value of the y property.
+ *
+ */
+ public void setY(double value) {
+ this.y = value;
+ }
+
+ /**
+ * Gets the value of the z property.
+ *
+ */
+ public double getZ() {
+ return z;
+ }
+
+ /**
+ * Sets the value of the z property.
+ *
+ */
+ public void setZ(double value) {
+ this.z = value;
+ }
+
+ }
+
+ }
+
+ }
+
+}
diff --git a/racevisionGame/src/main/java/shared/xml/boats/BoatDataSourceToXML.java b/racevisionGame/src/main/java/shared/xml/boats/BoatDataSourceToXML.java
new file mode 100644
index 00000000..d24bee56
--- /dev/null
+++ b/racevisionGame/src/main/java/shared/xml/boats/BoatDataSourceToXML.java
@@ -0,0 +1,86 @@
+package shared.xml.boats;
+
+import shared.dataInput.BoatDataSource;
+import shared.dataInput.RaceDataSource;
+import shared.enums.RoundingType;
+import shared.model.Boat;
+import shared.model.CompoundMark;
+import shared.model.Leg;
+import shared.model.Mark;
+import shared.xml.Race.*;
+import shared.xml.XMLUtilities;
+
+import javax.xml.bind.JAXBException;
+import java.time.format.DateTimeFormatter;
+import java.util.ArrayList;
+
+/**
+ * Has functions to convert a {@link shared.dataInput.BoatDataSource} to an {@link BoatConfig} object.
+ */
+public class BoatDataSourceToXML {
+
+
+ /**
+ * Converts a boat data source to an XMLRace object.
+ * @param boatDataSource The data source to convert.
+ * @return The XMLRace file.
+ */
+ public static BoatConfig toXML(BoatDataSource boatDataSource) {
+ BoatConfig boatConfig = new BoatConfig();
+
+ boatConfig.boats = new BoatConfig.Boats();
+ boatConfig.boats.boat = new ArrayList<>();
+
+
+ for (Boat boat : boatDataSource.getBoats().values()) {
+ BoatConfig.Boats.Boat xmlBoat = new BoatConfig.Boats.Boat();
+
+ xmlBoat.setType("Yacht");
+ xmlBoat.setBoatName(boat.getName());
+ xmlBoat.setSourceID(boat.getSourceID());
+ xmlBoat.setStoweName(boat.getCountry());
+ xmlBoat.setShortName(boat.getCountry());
+
+ BoatConfig.Boats.Boat.GPSposition position = new BoatConfig.Boats.Boat.GPSposition();
+ position.setX(boat.getPosition().getLongitude());
+ position.setY(boat.getPosition().getLatitude());
+ position.setZ(0);
+ xmlBoat.setGPSposition(position);
+
+ boatConfig.boats.boat.add(xmlBoat);
+ }
+
+
+ for (Mark mark : boatDataSource.getMarkerBoats().values()) {
+ BoatConfig.Boats.Boat xmlBoat = new BoatConfig.Boats.Boat();
+
+ xmlBoat.setType("Mark");
+ xmlBoat.setBoatName(mark.getName());
+ xmlBoat.setSourceID(mark.getSourceID());
+
+ BoatConfig.Boats.Boat.GPSposition position = new BoatConfig.Boats.Boat.GPSposition();
+ position.setX(mark.getPosition().getLongitude());
+ position.setY(mark.getPosition().getLatitude());
+ position.setZ(0);
+ xmlBoat.setGPSposition(position);
+
+ boatConfig.boats.boat.add(xmlBoat);
+ }
+
+ return boatConfig;
+ }
+
+
+ /**
+ * Converts a boat data source to an xml string.
+ * @param boatDataSource Data source to convert.
+ * @return String containing xml file.
+ * @throws JAXBException Thrown if it cannot be converted.
+ */
+ public static String toString(BoatDataSource boatDataSource) throws JAXBException {
+ BoatConfig boats = toXML(boatDataSource);
+ return XMLUtilities.classToXML(boats);
+ }
+
+
+}
diff --git a/racevisionGame/src/main/java/shared/xml/boats/ObjectFactory.java b/racevisionGame/src/main/java/shared/xml/boats/ObjectFactory.java
new file mode 100644
index 00000000..0319de9a
--- /dev/null
+++ b/racevisionGame/src/main/java/shared/xml/boats/ObjectFactory.java
@@ -0,0 +1,71 @@
+//
+// This file was generated by the JavaTM Architecture for XML Binding(JAXB) Reference Implementation, v2.2.8-b130911.1802
+// See http://java.sun.com/xml/jaxb
+// Any modifications to this file will be lost upon recompilation of the source schema.
+// Generated on: 2017.09.01 at 11:12:43 PM NZST
+//
+
+
+package shared.xml.boats;
+
+import javax.xml.bind.annotation.XmlRegistry;
+
+
+/**
+ * This object contains factory methods for each
+ * Java content interface and Java element interface
+ * generated in the aaa package.
+ * An ObjectFactory allows you to programatically
+ * construct new instances of the Java representation
+ * for XML content. The Java representation of XML
+ * content can consist of schema derived interfaces
+ * and classes representing the binding of schema
+ * type definitions, element declarations and model
+ * groups. Factory methods for each of these are
+ * provided in this class.
+ *
+ */
+@XmlRegistry
+public class ObjectFactory {
+
+
+ /**
+ * Create a new ObjectFactory that can be used to create new instances of schema derived classes for package: aaa
+ *
+ */
+ public ObjectFactory() {
+ }
+
+ /**
+ * Create an instance of {@link BoatConfig }
+ *
+ */
+ public BoatConfig createBoatConfig() {
+ return new BoatConfig();
+ }
+
+ /**
+ * Create an instance of {@link BoatConfig.Boats }
+ *
+ */
+ public BoatConfig.Boats createBoatConfigBoats() {
+ return new BoatConfig.Boats();
+ }
+
+ /**
+ * Create an instance of {@link BoatConfig.Boats.Boat }
+ *
+ */
+ public BoatConfig.Boats.Boat createBoatConfigBoatsBoat() {
+ return new BoatConfig.Boats.Boat();
+ }
+
+ /**
+ * Create an instance of {@link BoatConfig.Boats.Boat.GPSposition }
+ *
+ */
+ public BoatConfig.Boats.Boat.GPSposition createBoatConfigBoatsBoatGPSposition() {
+ return new BoatConfig.Boats.Boat.GPSposition();
+ }
+
+}
diff --git a/racevisionGame/src/main/java/shared/xml/regatta/ObjectFactory.java b/racevisionGame/src/main/java/shared/xml/regatta/ObjectFactory.java
new file mode 100644
index 00000000..7fc72202
--- /dev/null
+++ b/racevisionGame/src/main/java/shared/xml/regatta/ObjectFactory.java
@@ -0,0 +1,47 @@
+//
+// This file was generated by the JavaTM Architecture for XML Binding(JAXB) Reference Implementation, v2.2.8-b130911.1802
+// See http://java.sun.com/xml/jaxb
+// Any modifications to this file will be lost upon recompilation of the source schema.
+// Generated on: 2017.09.01 at 10:37:23 PM NZST
+//
+
+
+package shared.xml.regatta;
+
+import javax.xml.bind.annotation.XmlRegistry;
+
+
+/**
+ * This object contains factory methods for each
+ * Java content interface and Java element interface
+ * generated in the aaa package.
+ *
An ObjectFactory allows you to programatically
+ * construct new instances of the Java representation
+ * for XML content. The Java representation of XML
+ * content can consist of schema derived interfaces
+ * and classes representing the binding of schema
+ * type definitions, element declarations and model
+ * groups. Factory methods for each of these are
+ * provided in this class.
+ *
+ */
+@XmlRegistry
+public class ObjectFactory {
+
+
+ /**
+ * Create a new ObjectFactory that can be used to create new instances of schema derived classes for package: aaa
+ *
+ */
+ public ObjectFactory() {
+ }
+
+ /**
+ * Create an instance of {@link RegattaConfig }
+ *
+ */
+ public RegattaConfig createRegattaConfig() {
+ return new RegattaConfig();
+ }
+
+}
diff --git a/racevisionGame/src/main/java/shared/xml/regatta/RegattaConfig.java b/racevisionGame/src/main/java/shared/xml/regatta/RegattaConfig.java
new file mode 100644
index 00000000..d8cc1613
--- /dev/null
+++ b/racevisionGame/src/main/java/shared/xml/regatta/RegattaConfig.java
@@ -0,0 +1,219 @@
+//
+// This file was generated by the JavaTM Architecture for XML Binding(JAXB) Reference Implementation, v2.2.8-b130911.1802
+// See http://java.sun.com/xml/jaxb
+// Any modifications to this file will be lost upon recompilation of the source schema.
+// Generated on: 2017.09.01 at 10:37:23 PM NZST
+//
+
+
+package shared.xml.regatta;
+
+import javax.xml.bind.annotation.XmlAccessType;
+import javax.xml.bind.annotation.XmlAccessorType;
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.XmlRootElement;
+import javax.xml.bind.annotation.XmlType;
+
+
+/**
+ *
Java class for anonymous complex type.
+ *
+ *
The following schema fragment specifies the expected content contained within this class.
+ *
+ *
+ * <complexType>
+ * <complexContent>
+ * <restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
+ * <sequence>
+ * <element name="RegattaID" type="{http://www.w3.org/2001/XMLSchema}int"/>
+ * <element name="RegattaName" type="{http://www.w3.org/2001/XMLSchema}string"/>
+ * <element name="CourseName" type="{http://www.w3.org/2001/XMLSchema}string"/>
+ * <element name="CentralLatitude" type="{http://www.w3.org/2001/XMLSchema}double"/>
+ * <element name="CentralLongitude" type="{http://www.w3.org/2001/XMLSchema}double"/>
+ * <element name="CentralAltitude" type="{http://www.w3.org/2001/XMLSchema}double"/>
+ * <element name="UtcOffset" type="{http://www.w3.org/2001/XMLSchema}double"/>
+ * <element name="MagneticVariation" type="{http://www.w3.org/2001/XMLSchema}double"/>
+ * </sequence>
+ * </restriction>
+ * </complexContent>
+ * </complexType>
+ *
+ *
+ *
+ */
+@XmlAccessorType(XmlAccessType.FIELD)
+@XmlType(name = "", propOrder = {
+ "regattaID",
+ "regattaName",
+ "courseName",
+ "centralLatitude",
+ "centralLongitude",
+ "centralAltitude",
+ "utcOffset",
+ "magneticVariation"
+})
+@XmlRootElement(name = "RegattaConfig")
+public class RegattaConfig {
+
+ @XmlElement(name = "RegattaID")
+ protected int regattaID;
+ @XmlElement(name = "RegattaName", required = true)
+ protected String regattaName;
+ @XmlElement(name = "CourseName", required = true)
+ protected String courseName;
+ @XmlElement(name = "CentralLatitude")
+ protected double centralLatitude;
+ @XmlElement(name = "CentralLongitude")
+ protected double centralLongitude;
+ @XmlElement(name = "CentralAltitude")
+ protected double centralAltitude;
+ @XmlElement(name = "UtcOffset")
+ protected double utcOffset;
+ @XmlElement(name = "MagneticVariation")
+ protected double magneticVariation;
+
+ /**
+ * Gets the value of the regattaID property.
+ *
+ */
+ public int getRegattaID() {
+ return regattaID;
+ }
+
+ /**
+ * Sets the value of the regattaID property.
+ *
+ */
+ public void setRegattaID(int value) {
+ this.regattaID = value;
+ }
+
+ /**
+ * Gets the value of the regattaName property.
+ *
+ * @return
+ * possible object is
+ * {@link String }
+ *
+ */
+ public String getRegattaName() {
+ return regattaName;
+ }
+
+ /**
+ * Sets the value of the regattaName property.
+ *
+ * @param value
+ * allowed object is
+ * {@link String }
+ *
+ */
+ public void setRegattaName(String value) {
+ this.regattaName = value;
+ }
+
+ /**
+ * Gets the value of the courseName property.
+ *
+ * @return
+ * possible object is
+ * {@link String }
+ *
+ */
+ public String getCourseName() {
+ return courseName;
+ }
+
+ /**
+ * Sets the value of the courseName property.
+ *
+ * @param value
+ * allowed object is
+ * {@link String }
+ *
+ */
+ public void setCourseName(String value) {
+ this.courseName = value;
+ }
+
+ /**
+ * Gets the value of the centralLatitude property.
+ *
+ */
+ public double getCentralLatitude() {
+ return centralLatitude;
+ }
+
+ /**
+ * Sets the value of the centralLatitude property.
+ *
+ */
+ public void setCentralLatitude(double value) {
+ this.centralLatitude = value;
+ }
+
+ /**
+ * Gets the value of the centralLongitude property.
+ *
+ */
+ public double getCentralLongitude() {
+ return centralLongitude;
+ }
+
+ /**
+ * Sets the value of the centralLongitude property.
+ *
+ */
+ public void setCentralLongitude(double value) {
+ this.centralLongitude = value;
+ }
+
+ /**
+ * Gets the value of the centralAltitude property.
+ *
+ */
+ public double getCentralAltitude() {
+ return centralAltitude;
+ }
+
+ /**
+ * Sets the value of the centralAltitude property.
+ *
+ */
+ public void setCentralAltitude(double value) {
+ this.centralAltitude = value;
+ }
+
+ /**
+ * Gets the value of the utcOffset property.
+ *
+ */
+ public double getUtcOffset() {
+ return utcOffset;
+ }
+
+ /**
+ * Sets the value of the utcOffset property.
+ *
+ */
+ public void setUtcOffset(double value) {
+ this.utcOffset = value;
+ }
+
+ /**
+ * Gets the value of the magneticVariation property.
+ *
+ */
+ public double getMagneticVariation() {
+ return magneticVariation;
+ }
+
+ /**
+ * Sets the value of the magneticVariation property.
+ *
+ */
+ public void setMagneticVariation(double value) {
+ this.magneticVariation = value;
+ }
+
+}
diff --git a/racevisionGame/src/main/java/shared/xml/regatta/RegattaDataSourceToXML.java b/racevisionGame/src/main/java/shared/xml/regatta/RegattaDataSourceToXML.java
new file mode 100644
index 00000000..bbe1dce6
--- /dev/null
+++ b/racevisionGame/src/main/java/shared/xml/regatta/RegattaDataSourceToXML.java
@@ -0,0 +1,54 @@
+package shared.xml.regatta;
+
+import shared.dataInput.RegattaDataSource;
+import shared.xml.Race.XMLRace;
+import shared.xml.XMLUtilities;
+
+import javax.xml.bind.JAXBException;
+
+/**
+ * Has functions to convert a {@link shared.dataInput.RegattaDataSource} to an {@link RegattaConfig} object.
+ */
+public class RegattaDataSourceToXML {
+
+
+ /**
+ * Converts a regatta data source to an XMLRace object.
+ * @param regattaDataSource The data source to convert.
+ * @return The XMLRace file.
+ */
+ public static RegattaConfig toXML(RegattaDataSource regattaDataSource) {
+
+ RegattaConfig regatta = new RegattaConfig();
+
+ regatta.setCentralAltitude(regattaDataSource.getCentralAltitude());
+ regatta.setCentralLatitude(regattaDataSource.getCentralLatitude());
+ regatta.setCentralLongitude(regattaDataSource.getCentralLongitude());
+
+ regatta.setCourseName(regattaDataSource.getCourseName());
+
+ regatta.setRegattaName(regattaDataSource.getRegattaName());
+
+ regatta.setMagneticVariation(regattaDataSource.getMagneticVariation());
+
+ regatta.setRegattaID(regattaDataSource.getRegattaID());
+
+ regatta.setUtcOffset(regattaDataSource.getUtcOffset());
+
+ return regatta;
+ }
+
+
+ /**
+ * Converts a regatta data source to an xml string.
+ * @param regattaDataSource Data source to convert.
+ * @return String containing xml file.
+ * @throws JAXBException Thrown if it cannot be converted.
+ */
+ public static String toString(RegattaDataSource regattaDataSource) throws JAXBException {
+ RegattaConfig regatta = toXML(regattaDataSource);
+ return XMLUtilities.classToXML(regatta);
+ }
+
+
+}
diff --git a/racevisionGame/src/main/java/visualiser/Controllers/FinishController.java b/racevisionGame/src/main/java/visualiser/Controllers/FinishController.java
index c5356495..6de6dcdf 100644
--- a/racevisionGame/src/main/java/visualiser/Controllers/FinishController.java
+++ b/racevisionGame/src/main/java/visualiser/Controllers/FinishController.java
@@ -71,8 +71,10 @@ public class FinishController extends Controller {
//Winner label.
- raceWinnerLabel.setText("Winner: "+ boatNameColumn.getCellObservableValue(0).getValue());
- raceWinnerLabel.setWrapText(true);
+ if (boats.size() > 0) {
+ raceWinnerLabel.setText("Winner: " + boatNameColumn.getCellObservableValue(0).getValue());
+ raceWinnerLabel.setWrapText(true);
+ }
}
diff --git a/racevisionGame/src/main/java/visualiser/Controllers/KeyBindingsController.java b/racevisionGame/src/main/java/visualiser/Controllers/KeyBindingsController.java
new file mode 100644
index 00000000..84d82e91
--- /dev/null
+++ b/racevisionGame/src/main/java/visualiser/Controllers/KeyBindingsController.java
@@ -0,0 +1,247 @@
+package visualiser.Controllers;
+
+import javafx.application.Platform;
+import javafx.fxml.FXML;
+import javafx.fxml.FXMLLoader;
+import javafx.scene.Parent;
+import javafx.scene.Scene;
+import javafx.scene.control.Button;
+import javafx.scene.control.ListView;
+import javafx.scene.input.KeyCode;
+import javafx.scene.input.KeyEvent;
+import javafx.scene.layout.AnchorPane;
+import javafx.stage.Modality;
+import javafx.stage.Stage;
+import javafx.stage.WindowEvent;
+import visualiser.gameController.Keys.ControlKey;
+import visualiser.gameController.Keys.KeyFactory;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+
+import static visualiser.app.App.keyFactory;
+
+/**
+ * Controller for the scene used to display and update current key bindings.
+ */
+public class KeyBindingsController {
+ private @FXML Button btnSave;
+ private @FXML Button btnCancel;
+ private @FXML Button btnReset;
+ private @FXML ListView lstControl;
+ private @FXML ListView lstKey;
+ private @FXML ListView lstDescription;
+ private @FXML AnchorPane anchor;
+ private KeyFactory newKeyFactory;
+ private Boolean changed = false; // keyBindings have been modified
+ private Button currentButton = null; // last button clicked
+
+ public void initialize(){
+ // create new key factory to modify, keeping the existing one safe
+ newKeyFactory = copyExistingFactory();
+ initializeTable();
+ populateTable();
+ setKeyListener();
+ }
+
+ /**
+ * Sets up table before populating it.
+ * Set up includes headings, CSS styling and modifying default properties.
+ */
+ public void initializeTable(){
+ // set the headings for each column
+ lstKey.getItems().add("Key");
+ lstControl.getItems().add("Command");
+ lstDescription.getItems().add("Description");
+ lstKey.getSelectionModel().select(0);
+ lstControl.getSelectionModel().select(0);
+ lstDescription.getSelectionModel().select(0);
+
+ // add CSS stylesheet once the scene has been created
+ lstKey.sceneProperty().addListener((obs, oldScene, newScene) -> {
+ if (newScene != null) {
+ newScene.getStylesheets().add("/css/keyBindings.css");
+ }
+ });
+
+ // stop the columns from being selectable, so only the buttons are
+ lstKey.getSelectionModel().selectedItemProperty()
+ .addListener((observable, oldvalue, newValue) ->
+ Platform.runLater(() ->
+ lstKey.getSelectionModel().select(0)));
+ lstDescription.getSelectionModel().selectedItemProperty()
+ .addListener((observable, oldvalue, newValue) ->
+ Platform.runLater(() ->
+ lstDescription.getSelectionModel().select(0)));
+ lstControl.getSelectionModel().selectedItemProperty()
+ .addListener((observable, oldvalue, newValue) ->
+ Platform.runLater(() ->
+ lstControl.getSelectionModel().select(0)));
+ }
+
+ /**
+ * Populates the table with commands and their key binding details.
+ */
+ public void populateTable(){
+ // add each command to the table
+ for (Map.Entry entry : newKeyFactory.getKeyState().entrySet()) {
+ // create button for command
+ Button button = new Button(entry.getKey());
+ button.setMinWidth(120);
+ button.setId(entry.getValue().toString());
+ button.setOnAction(e -> currentButton = button);
+ // display details for command in table
+ lstControl.getItems().add(entry.getValue());
+ lstKey.getItems().add(button);
+ lstDescription.getItems().add(entry.getValue().getProtocolCode());
+ }
+ }
+
+ /**
+ * Makes a copy of the {@link KeyFactory} that does not modify the original.
+ * @return new keyfactory to be modified
+ */
+ public KeyFactory copyExistingFactory(){
+ newKeyFactory = new KeyFactory();
+ Map oldKeyState = keyFactory.getKeyState();
+ Map newKeyState = new HashMap<>();
+
+ // copy over commands and their keys
+ for (Map.Entry entry : oldKeyState.entrySet()){
+ newKeyState.put(entry.getKey(), entry.getValue());
+ }
+ newKeyFactory.setKeyState(newKeyState);
+ return newKeyFactory;
+ }
+
+ /**
+ * Creates a listener for the base anchorpane for key presses.
+ * It updates the current key bindings of the {@link KeyFactory} if
+ * required.
+ */
+ public void setKeyListener(){
+ anchor.addEventFilter(KeyEvent.KEY_PRESSED, event -> {
+ // if esc, cancel current button click
+ if (event.getCode() == KeyCode.ESCAPE){
+ btnCancel.requestFocus();
+ currentButton = null;
+ }
+ // if a button was clicked
+ else if (currentButton != null) {
+ // check if a button is already mapped to this key
+ for (int i = 1; i < lstKey.getItems().size(); i++) {
+ Button button = (Button)lstKey.getItems().get(i);
+ // update buttons text and remove key binding from command
+ if (button.getText().equals(event.getCode().toString())) {
+ button.setText("");
+ newKeyFactory.updateKey(button.getId(), button.getId());
+ }
+ }
+ // update text on the button
+ currentButton.setText(event.getCode().toString());
+ // update the control key
+ newKeyFactory.updateKey(event.getCode().toString(),
+ currentButton.getId());
+ // remove current button selection
+ currentButton = null;
+ changed = true;
+ btnCancel.requestFocus();
+ }
+ event.consume();
+ });
+ }
+
+ /**
+ * Cancel and exits the key bindings menu. Changes are not forced to be
+ * saved or fixed if invalid, and instead are defaulted back to the last
+ * successful saved state.
+ */
+ public void cancel(){
+ ((Stage)btnCancel.getScene().getWindow()).close();
+ }
+
+ /**
+ * Resets all key bindings to the built-in defaults.
+ */
+ public void reset(){
+ lstKey.getItems().clear();
+ lstControl.getItems().clear();
+ lstDescription.getItems().clear();
+ newKeyFactory = new KeyFactory();
+ initializeTable();
+ populateTable();
+ changed = true;
+ }
+
+ /**
+ * Replace existing {@link KeyFactory} with the modified key bindings.
+ */
+ public void save(){
+ if (isFactoryValid()) {
+ keyFactory = newKeyFactory;
+ newKeyFactory = new KeyFactory();
+ changed = false;
+ keyFactory.save(); // save persistently
+ loadNotification("Key bindings were successfully saved.", false);
+ } else {
+ loadNotification("One or more key bindings are missing. " +
+ "Failed to save.", true);
+ }
+ }
+
+ /**
+ * Checks the {@link KeyFactory} being modified is valid and that no
+ * commands are missing a key binding.
+ * @return True if valid, false if invalid
+ */
+ public Boolean isFactoryValid(){
+ for (Map.Entry entry : newKeyFactory.getKeyState().entrySet
+ ()) {
+ if (entry.getKey().toString()==entry.getValue().toString()){
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Method used to stop a user from exiting key bindings without saving
+ * their changes to the {@link KeyFactory}.
+ * @param we {@link WindowEvent} close request to be consumed if settings
+ * have not been successfully saved.
+ */
+ public void onExit(WindowEvent we){
+ // if modified KeyFactory hasn't been saved
+ if (changed){
+ loadNotification("Please cancel or save your changes before exiting" +
+ ".", true);
+ we.consume();
+ }
+ }
+
+ /**
+ * Loads a popup window giving confirmation/warning of user activity.
+ * @param message the message to be displayed to the user
+ * @param warning true if the message to be displayed is due to user error
+ */
+ public void loadNotification(String message, Boolean warning){
+ Parent root = null;
+ FXMLLoader loader = new FXMLLoader(getClass().getResource
+ ("/visualiser/scenes/notification.fxml"));
+ try {
+ root = loader.load();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ NotificationController controller = loader.getController();
+ Stage stage = new Stage();
+ stage.setScene(new Scene(root));
+ stage.centerOnScreen();
+ stage.initModality(Modality.APPLICATION_MODAL);
+ stage.show();
+ // displays given message in the window
+ controller.setMessage(message, warning);
+ }
+
+}
diff --git a/racevisionGame/src/main/java/visualiser/Controllers/LobbyController.java b/racevisionGame/src/main/java/visualiser/Controllers/LobbyController.java
index 07af2b49..d541c531 100644
--- a/racevisionGame/src/main/java/visualiser/Controllers/LobbyController.java
+++ b/racevisionGame/src/main/java/visualiser/Controllers/LobbyController.java
@@ -21,7 +21,7 @@ import java.util.ResourceBundle;
public class LobbyController extends Controller {
@FXML
- AnchorPane lobbyWrapper;
+ private AnchorPane lobbyWrapper;
@FXML
private TableView lobbyTable;
@FXML
diff --git a/racevisionGame/src/main/java/visualiser/Controllers/MainController.java b/racevisionGame/src/main/java/visualiser/Controllers/MainController.java
index 57d18830..e1aa1ede 100644
--- a/racevisionGame/src/main/java/visualiser/Controllers/MainController.java
+++ b/racevisionGame/src/main/java/visualiser/Controllers/MainController.java
@@ -3,7 +3,6 @@ package visualiser.Controllers;
import javafx.collections.ObservableList;
import javafx.fxml.FXML;
import javafx.scene.layout.AnchorPane;
-import visualiser.app.App;
import visualiser.gameController.ControllerClient;
import visualiser.model.VisualiserBoat;
import visualiser.model.VisualiserRaceEvent;
@@ -40,6 +39,7 @@ public class MainController extends Controller {
* Transitions from the StartController screen (displays pre-race information) to the RaceController (displays the actual race).
* @param visualiserRace The object modelling the race.
* @param controllerClient Socket Client that manipulates the controller.
+ * @param isHost if the client is the host of a race or not.
*/
public void beginRace(VisualiserRaceEvent visualiserRace, ControllerClient controllerClient, Boolean isHost) {
raceController.startRace(visualiserRace, controllerClient, isHost);
diff --git a/racevisionGame/src/main/java/visualiser/Controllers/NotificationController.java b/racevisionGame/src/main/java/visualiser/Controllers/NotificationController.java
new file mode 100644
index 00000000..d122ec80
--- /dev/null
+++ b/racevisionGame/src/main/java/visualiser/Controllers/NotificationController.java
@@ -0,0 +1,35 @@
+package visualiser.Controllers;
+
+import javafx.fxml.FXML;
+import javafx.scene.control.Label;
+import javafx.scene.paint.Color;
+import javafx.scene.text.Text;
+import javafx.stage.Stage;
+
+/**
+ * Controller for a popup notification regarding user activity.
+ */
+public class NotificationController {
+ private @FXML Label lblDescription;
+ private @FXML Text txtMessage;
+
+ /**
+ * Closes the popup window once clicked.
+ */
+ public void ok(){
+ ((Stage)lblDescription.getScene().getWindow()).close();
+ }
+
+ /**
+ * Displays the appropriate popup notification.
+ * @param message message for the user
+ * @param warning if true warning text shown, if false success text shown
+ */
+ public void setMessage(String message, Boolean warning){
+ lblDescription.setText(message);
+ if (!warning){
+ txtMessage.setText("Success!");
+ txtMessage.setFill(Color.GREEN);
+ }
+ }
+}
diff --git a/racevisionGame/src/main/java/visualiser/Controllers/RaceController.java b/racevisionGame/src/main/java/visualiser/Controllers/RaceController.java
index 4fcda581..e864993d 100644
--- a/racevisionGame/src/main/java/visualiser/Controllers/RaceController.java
+++ b/racevisionGame/src/main/java/visualiser/Controllers/RaceController.java
@@ -1,6 +1,6 @@
package visualiser.Controllers;
-
+import com.interactivemesh.jfx.importer.stl.StlMeshImporter;
import javafx.animation.AnimationTimer;
import javafx.application.Platform;
import javafx.collections.FXCollections;
@@ -10,21 +10,27 @@ import javafx.collections.transformation.SortedList;
import javafx.fxml.FXML;
import javafx.scene.chart.LineChart;
import javafx.scene.control.*;
-import javafx.scene.control.Label;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
-import javafx.scene.layout.AnchorPane;
+import javafx.scene.input.ScrollEvent;
import javafx.scene.layout.GridPane;
-import javafx.scene.layout.Pane;
import javafx.scene.layout.StackPane;
+import javafx.scene.shape.MeshView;
+import javafx.scene.shape.Sphere;
+import javafx.scene.transform.Translate;
import javafx.util.Callback;
import network.Messages.Enums.RaceStatusEnum;
+import shared.dataInput.RaceDataSource;
import shared.model.Leg;
+import shared.model.Mark;
import visualiser.app.App;
import visualiser.gameController.ControllerClient;
import visualiser.gameController.Keys.ControlKey;
import visualiser.gameController.Keys.KeyFactory;
+import visualiser.layout.Subject3D;
+import visualiser.layout.View3D;
import visualiser.model.*;
+import visualiser.utils.GPSConverter;
import java.io.IOException;
import java.net.URL;
@@ -33,59 +39,41 @@ import java.util.ResourceBundle;
import java.util.logging.Level;
import java.util.logging.Logger;
+import static visualiser.app.App.keyFactory;
/**
* Controller used to display a running race.
*/
public class RaceController extends Controller {
-
-
/**
* The race object which describes the currently occurring race.
*/
private VisualiserRaceEvent visualiserRace;
-
/**
* Service for sending keystrokes to server
*/
private ControllerClient controllerClient;
-
private boolean isHost;
- /**
- * The canvas that draws the race.
- */
- private ResizableRaceCanvas raceCanvas;
-
- /**
- * The sparkline graph.
- */
- private Sparkline sparkline;
-
/**
* state of the info table
*/
private boolean infoTableShow;
+ private View3D view3D;
+ private ObservableList viewSubjects;
+
/**
* The arrow controller.
*/
@FXML private ArrowController arrowController;
-
-
@FXML private GridPane canvasBase;
-
- @FXML private SplitPane race;
-
- /**
- * This is the root node of the arrow control.
- */
- @FXML private Pane arrow;
+ @FXML private SplitPane racePane;
/**
* This is the pane we place the actual arrow control inside of.
@@ -101,9 +89,6 @@ public class RaceController extends Controller {
@FXML private TableColumn boatMarkColumn;
@FXML private TableColumn boatSpeedColumn;
@FXML private LineChart sparklineChart;
- @FXML private AnchorPane annotationPane;
-
-
/**
* Ctor.
@@ -113,11 +98,10 @@ public class RaceController extends Controller {
@Override
public void initialize(URL location, ResourceBundle resources) {
- KeyFactory keyFactory = KeyFactory.getFactory();
infoTableShow = true;
// Initialise keyboard handler
- race.addEventFilter(KeyEvent.KEY_PRESSED, event -> {
+ racePane.addEventFilter(KeyEvent.KEY_PRESSED, event -> {
String codeString = event.getCode().toString();
if (codeString.equals("TAB")){toggleTable();}
@@ -143,7 +127,7 @@ public class RaceController extends Controller {
Optional result = alert.showAndWait();
if (result.get() == ButtonType.OK) {
parent.endEvent();
- race.setVisible(false);
+ racePane.setVisible(false);
App.app.showMainStage(App.getStage());
}
} else {
@@ -152,7 +136,7 @@ public class RaceController extends Controller {
alert.setContentText("Do you wish to quit the race?");
Optional result = alert.showAndWait();
if (result.get() == ButtonType.OK) {
- race.setVisible(false);
+ racePane.setVisible(false);
App.app.showMainStage(App.getStage());
}
}
@@ -176,14 +160,10 @@ public class RaceController extends Controller {
//Information table.
initialiseInfoTable(this.visualiserRace);
- //Sparkline.
- initialiseSparkline(this.visualiserRace);
-
//Arrow.
initialiseArrow(this.visualiserRace);
- //Race canvas.
- initialiseRaceCanvas(this.visualiserRace);
+ initialiseView3D(this.visualiserRace);
//Race timezone label.
initialiseRaceTimezoneLabel(this.visualiserRace);
@@ -191,11 +171,79 @@ public class RaceController extends Controller {
//Race clock.
initialiseRaceClock(this.visualiserRace);
-
//Start the race animation timer.
raceTimer();
}
+ private void initialiseView3D(VisualiserRaceEvent race) {
+ viewSubjects = FXCollections.observableArrayList();
+
+ // Import boat mesh
+ URL asset = HostController.class.getClassLoader().getResource("assets/V1.2 Complete Boat.stl");
+ StlMeshImporter importer = new StlMeshImporter();
+ importer.read(asset);
+
+ // Configure camera angles and control
+ view3D = new View3D();
+ view3D.setDistance(1050);
+ view3D.setYaw(0);
+ view3D.setPitch(60);
+ view3D.enableTracking();
+ canvasBase.add(view3D, 0, 0);
+
+ // Set up projection from GPS to view
+ RaceDataSource raceData = visualiserRace.getVisualiserRaceState().getRaceDataSource();
+ final GPSConverter gpsConverter = new GPSConverter(raceData, 450, 450);
+
+ view3D.setItems(viewSubjects);
+ // Position and add each mark to view
+ for(Mark mark: race.getVisualiserRaceState().getMarks()) {
+ Subject3D subject = new Subject3D(new Sphere(2));
+ subject.setX(gpsConverter.convertGPS(mark.getPosition()).getX());
+ subject.setZ(gpsConverter.convertGPS(mark.getPosition()).getY());
+
+ viewSubjects.add(subject);
+ }
+ // Position and add each boat to view
+ for(VisualiserBoat boat: race.getVisualiserRaceState().getBoats()) {
+ MeshView mesh = new MeshView(importer.getImport());
+ Subject3D subject = new Subject3D(mesh);
+ viewSubjects.add(subject);
+
+ // Track this boat's movement with the new subject
+ AnimationTimer trackBoat = new AnimationTimer() {
+ @Override
+ public void handle(long now) {
+ subject.setHeading(boat.getBearing().degrees());
+ subject.setX(gpsConverter.convertGPS(boat.getPosition()).getX());
+ subject.setZ(gpsConverter.convertGPS(boat.getPosition()).getY());
+ }
+ };
+ trackBoat.start();
+ }
+ // Fix initial bird's-eye position
+ view3D.updatePivot(new Translate(250, 0, 210));
+
+ // Bind zooming to scrolling
+ view3D.setOnScroll(e -> {
+ view3D.updateDistance(e.getDeltaY());
+ });
+
+ // Bind zooming to keypress (Z/X default)
+ racePane.addEventFilter(KeyEvent.KEY_PRESSED, e -> {
+ ControlKey key = keyFactory.getKey(e.getCode().toString());
+ if(key != null) {
+ switch (key.toString()) {
+ case "Zoom In":
+ view3D.updateDistance(-10);
+ break;
+ case "Zoom Out":
+ view3D.updateDistance(10);
+ break;
+ }
+ }
+ });
+ }
/**
@@ -338,42 +386,8 @@ public class RaceController extends Controller {
}
-
- /**
- * Initialises the {@link Sparkline}, and listens to a specified {@link VisualiserRaceEvent}.
- * @param race The race to listen to.
- */
- private void initialiseSparkline(VisualiserRaceEvent race) {
- //The race.getBoats() we are passing in is sorted by position in race inside the race class.
- this.sparkline = new Sparkline(this.visualiserRace.getVisualiserRaceState(), this.sparklineChart);
- }
-
-
- /**
- * Initialises the {@link ResizableRaceCanvas}, provides the race to read data from.
- * @param race Race to read data from.
- */
- private void initialiseRaceCanvas(VisualiserRaceEvent race) {
-
- //Create canvas.
- raceCanvas = new ResizableRaceCanvas(race);
-
- //Set properties.
- raceCanvas.setMouseTransparent(true);
- raceCanvas.widthProperty().bind(canvasBase.widthProperty());
- raceCanvas.heightProperty().bind(canvasBase.heightProperty());
-
- //Draw it and show it.
- raceCanvas.draw();
- raceCanvas.setVisible(true);
-
- //Add to scene.
- canvasBase.getChildren().add(0, raceCanvas);
- }
-
-
/**
- * Intialises the race time zone label with the race's time zone.
+ * Initialises the race time zone label with the race's time zone.
* @param race The race to get time zone from.
*/
private void initialiseRaceTimezoneLabel(VisualiserRaceEvent race) {
@@ -411,11 +425,7 @@ public class RaceController extends Controller {
initialiseRace();
//Display this controller.
- race.setVisible(true);
-
-
- // set up annotation displays
- new Annotations(annotationPane, raceCanvas);
+ racePane.setVisible(true);
}
/**
@@ -423,7 +433,7 @@ public class RaceController extends Controller {
* @param boats boats there are in the race.
*/
public void finishRace(ObservableList boats) {
- race.setVisible(false);
+ racePane.setVisible(false);
parent.enterFinish(boats);
}
@@ -458,18 +468,13 @@ public class RaceController extends Controller {
finishRace(visualiserRace.getVisualiserRaceState().getBoats());
} else {
- //Otherwise, render the canvas.
- raceCanvas.drawRace();
-
-
//Sort the tableview. Doesn't automatically work for all columns.
boatInfoTable.sort();
-
}
//Return to main screen if we lose connection.
if (!visualiserRace.getServerConnection().isAlive()) {
- race.setVisible(false);
+ racePane.setVisible(false);
//parent.enterTitle();
try {
App.app.showMainStage(App.getStage());
@@ -491,10 +496,10 @@ public class RaceController extends Controller {
* toggles if the info table is shown
*/
private void toggleTable() {
- double tablePercent = 1 - (boatPlacingColumn.getPrefWidth() + boatTeamColumn.getPrefWidth() + boatMarkColumn.getPrefWidth() + boatSpeedColumn.getPrefWidth())/race.getWidth();
+ double tablePercent = 1 - (boatPlacingColumn.getPrefWidth() + boatTeamColumn.getPrefWidth() + boatMarkColumn.getPrefWidth() + boatSpeedColumn.getPrefWidth())/racePane.getWidth();
if (infoTableShow){
- race.setDividerPositions(tablePercent);
+ racePane.setDividerPositions(tablePercent);
arrowPane.setScaleX(0.5);
arrowPane.setScaleY(0.5);
@@ -502,7 +507,7 @@ public class RaceController extends Controller {
arrowPane.setTranslateY(0 - arrowPane.getScene().getHeight()/4);
}else{
- race.setDividerPositions(1);
+ racePane.setDividerPositions(1);
arrowPane.setScaleX(1);
arrowPane.setScaleY(1);
diff --git a/racevisionGame/src/main/java/visualiser/Controllers/StartController.java b/racevisionGame/src/main/java/visualiser/Controllers/StartController.java
index 890eb816..b46bf79f 100644
--- a/racevisionGame/src/main/java/visualiser/Controllers/StartController.java
+++ b/racevisionGame/src/main/java/visualiser/Controllers/StartController.java
@@ -225,12 +225,10 @@ public class StartController extends Controller {
//Get the current race status.
RaceStatusEnum raceStatus = visualiserRaceEvent.getVisualiserRaceState().getRaceStatusEnum();
- //Display it.
- raceStatusLabel.setText("Race Status: " + raceStatus.name());
-
-
//If the race has reached the preparatory phase, or has started...
- if (raceStatus == RaceStatusEnum.PREPARATORY || raceStatus == RaceStatusEnum.STARTED) {
+ if (raceStatus == RaceStatusEnum.WARNING
+ || raceStatus == RaceStatusEnum.PREPARATORY
+ || raceStatus == RaceStatusEnum.STARTED) {
//Stop this timer.
stop();
diff --git a/racevisionGame/src/main/java/visualiser/Controllers/TitleController.java b/racevisionGame/src/main/java/visualiser/Controllers/TitleController.java
index 32e033c8..aa2edf13 100644
--- a/racevisionGame/src/main/java/visualiser/Controllers/TitleController.java
+++ b/racevisionGame/src/main/java/visualiser/Controllers/TitleController.java
@@ -1,5 +1,6 @@
package visualiser.Controllers;
+import javafx.event.EventHandler;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
@@ -9,6 +10,7 @@ import javafx.scene.control.RadioButton;
import javafx.scene.layout.AnchorPane;
import javafx.stage.Modality;
import javafx.stage.Stage;
+import javafx.stage.WindowEvent;
import visualiser.app.App;
import java.io.IOException;
@@ -81,25 +83,32 @@ public class TitleController extends Controller {
@Override
public void initialize(URL location, ResourceBundle resources) {
-
}
/**
* Called when control button is pressed. New pop up window displaying controls
*/
public void controlBtnPressed(){
- FXMLLoader loader = new FXMLLoader();
- loader.setLocation(getClass().getResource("/visualiser/scenes/controls.fxml"));
- Parent layout;
try {
- layout = loader.load();
+ FXMLLoader loader = new FXMLLoader();
+ loader.setLocation(getClass().getResource("/visualiser/scenes/keyBindings.fxml"));
+ Parent layout = loader.load();
Scene scene = new Scene(layout);
Stage popupStage = new Stage();
popupStage.setResizable(false);
popupStage.setTitle("Game Controls");
popupStage.initModality(Modality.WINDOW_MODAL);
+ popupStage.centerOnScreen();
popupStage.setScene(scene);
- popupStage.showAndWait();
+ popupStage.show();
+ KeyBindingsController controller = loader.getController();
+ popupStage.setOnCloseRequest(new EventHandler() {
+ public void handle(WindowEvent we) {
+ if (we.getEventType() == WindowEvent.WINDOW_CLOSE_REQUEST) {
+ controller.onExit(we);
+ }
+ }
+ });
} catch (Exception e){
e.printStackTrace();
}
diff --git a/racevisionGame/src/main/java/visualiser/app/App.java b/racevisionGame/src/main/java/visualiser/app/App.java
index eeae5a5d..e095e04e 100644
--- a/racevisionGame/src/main/java/visualiser/app/App.java
+++ b/racevisionGame/src/main/java/visualiser/app/App.java
@@ -3,7 +3,6 @@ package visualiser.app;
import javafx.animation.FadeTransition;
import javafx.application.Application;
import javafx.application.Platform;
-import javafx.beans.property.ReadOnlyObjectProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.concurrent.Task;
@@ -15,7 +14,6 @@ import javafx.geometry.Rectangle2D;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.Label;
-import javafx.scene.control.ListView;
import javafx.scene.control.ProgressBar;
import javafx.scene.effect.DropShadow;
import javafx.scene.image.Image;
@@ -29,19 +27,17 @@ import javafx.stage.StageStyle;
import javafx.stage.WindowEvent;
import javafx.util.Duration;
import visualiser.Controllers.MainController;
-
-import java.io.IOException;
+import visualiser.gameController.Keys.KeyFactory;
public class App extends Application {
-
private static Stage stage;
private Pane splashLayout;
private ProgressBar loadProgress;
private Label progressText;
private static final int SPLASH_WIDTH = 676;
private static final int SPLASH_HEIGHT = 227;
-
+ public static KeyFactory keyFactory = new KeyFactory();
public static App app;
/**
@@ -55,6 +51,9 @@ public class App extends Application {
@Override
public void init() {
+ // load the user's personalised key bindings
+ keyFactory.load();
+
ImageView splash = new ImageView(new Image(
getClass().getClassLoader().getResourceAsStream("images/splashScreen.png")
));
@@ -96,15 +95,15 @@ public class App extends Application {
);
updateMessage("Preparing ingredients . . .");
- Thread.sleep(1000);
+ Thread.sleep(200);
for (int i = 0; i < burgerFilling.size(); i++) {
- Thread.sleep(800);
+ Thread.sleep(100);
updateProgress(i + 1, burgerFilling.size());
String nextFilling = burgerFilling.get(i);
addedFilling.add(nextFilling);
updateMessage("Adding the " + nextFilling + " . . .");
}
- Thread.sleep(400);
+ Thread.sleep(100);
updateMessage("Burger's done!");
return addedFilling;
diff --git a/racevisionGame/src/main/java/visualiser/gameController/InputChecker.java b/racevisionGame/src/main/java/visualiser/gameController/InputChecker.java
index 057b5721..b4b3de9b 100644
--- a/racevisionGame/src/main/java/visualiser/gameController/InputChecker.java
+++ b/racevisionGame/src/main/java/visualiser/gameController/InputChecker.java
@@ -3,10 +3,11 @@ package visualiser.gameController;
import javafx.animation.AnimationTimer;
import javafx.scene.Scene;
import visualiser.gameController.Keys.ControlKey;
-import visualiser.gameController.Keys.KeyFactory;
import java.util.HashMap;
+import static visualiser.app.App.keyFactory;
+
/**
* Class for checking what keys are currently being used
*/
@@ -18,7 +19,7 @@ public class InputChecker {
* @param scene Scene the controller is to run in parallel with.
*/
public void runWithScene(Scene scene){
- KeyFactory keyFactory = KeyFactory.getFactory();
+// KeyFactory keyFactory = KeyFactory.getFactory();
scene.setOnKeyPressed(event -> {
String codeString = event.getCode().toString();
diff --git a/racevisionGame/src/main/java/visualiser/gameController/Keys/ControlKey.java b/racevisionGame/src/main/java/visualiser/gameController/Keys/ControlKey.java
index dd489f73..ce4b341e 100644
--- a/racevisionGame/src/main/java/visualiser/gameController/Keys/ControlKey.java
+++ b/racevisionGame/src/main/java/visualiser/gameController/Keys/ControlKey.java
@@ -1,6 +1,5 @@
package visualiser.gameController.Keys;
-import javafx.scene.input.KeyCode;
import network.Messages.Enums.BoatActionEnum;
/**
@@ -45,7 +44,7 @@ public abstract class ControlKey {
/**
* What this key should do when the command is issued for it to do its job.
*/
- public abstract void onAction();//may want to make it take in a visualiser and stuff in the future.
+ public abstract void onAction();
/**
* What to do when the key is held
diff --git a/racevisionGame/src/main/java/visualiser/gameController/Keys/DownWindKey.java b/racevisionGame/src/main/java/visualiser/gameController/Keys/DownWindKey.java
index e4b5455a..6c974972 100644
--- a/racevisionGame/src/main/java/visualiser/gameController/Keys/DownWindKey.java
+++ b/racevisionGame/src/main/java/visualiser/gameController/Keys/DownWindKey.java
@@ -9,11 +9,9 @@ public class DownWindKey extends ControlKey {
/**
* Constructor for Control
- * @param name name of the key
- *
*/
- public DownWindKey(String name) {
- super(name, BoatActionEnum.DOWNWIND);
+ public DownWindKey() {
+ super("Downwind", BoatActionEnum.DOWNWIND);
}
@Override
diff --git a/racevisionGame/src/main/java/visualiser/gameController/Keys/KeyFactory.java b/racevisionGame/src/main/java/visualiser/gameController/Keys/KeyFactory.java
index be95abd3..a783f268 100644
--- a/racevisionGame/src/main/java/visualiser/gameController/Keys/KeyFactory.java
+++ b/racevisionGame/src/main/java/visualiser/gameController/Keys/KeyFactory.java
@@ -1,5 +1,8 @@
package visualiser.gameController.Keys;
+import java.beans.XMLDecoder;
+import java.beans.XMLEncoder;
+import java.io.*;
import java.util.HashMap;
import java.util.Map;
@@ -13,39 +16,108 @@ public class KeyFactory {
private Map keyState;
/**
- * Singleton instance to enforce consistent key state
+ * Constructor for key state, set up initial state of each action.
*/
- private static KeyFactory theFactory = new KeyFactory();
+ public KeyFactory() {
+ this.keyState = new HashMap<>();
+ keyState.put("Z", new ZoomInKey());
+ keyState.put("X", new ZoomOutKey());
+ keyState.put("SPACE", new VMGKey());
+ keyState.put("SHIFT", new SailsToggleKey());
+ keyState.put("ENTER", new TackGybeKey());
+ keyState.put("UP", new UpWindKey());
+ keyState.put("DOWN", new DownWindKey());
+ }
/**
- * Singleton constructor for key state, set up initial state of each action.
+ * Get the Control Key in charge of a key press
+ * @param key key pressed (String value of KeyCode)
+ * @return the Control Key behaviour of the key pressed.
*/
- private KeyFactory() {
- this.keyState = new HashMap<>();
- keyState.put("Z", new ZoomInKey("Zoom In"));
- keyState.put("X", new ZoomOutKey("Zoom Out"));
- keyState.put("SPACE", new VMGKey("VMG"));
- keyState.put("SHIFT", new SailsToggleKey("Toggle Sails"));
- keyState.put("ENTER", new TackGybeKey("Tack/Gybe"));
- keyState.put("UP", new UpWindKey("Upwind"));
- keyState.put("DOWN", new DownWindKey("Downwind"));
+ public ControlKey getKey(String key){
+ return keyState.get(key);
+ }
+
+ public Map getKeyState() {
+ return keyState;
+ }
+
+ public void setKeyState(Map keyState) {
+ this.keyState = keyState;
}
/**
- * Get singleton instance of KeyFactory to interact with key state
- * @return automatically constructed KeyFactory
+ * Update the key bound to a particular command in the keystate.
+ * @param newKey the new key value for the command
+ * @param command the command to be updated
*/
- public static KeyFactory getFactory() {
- return theFactory;
+ public void updateKey(String newKey, String command){
+ ControlKey controlKey = null;
+ String oldKey = null;
+ for (Map.Entry entry : keyState.entrySet()) {
+ // if this is the correct command
+ if (entry.getValue().toString()==command){
+ controlKey = entry.getValue();
+ oldKey = entry.getKey();
+ }
+ }
+ keyState.remove(oldKey, controlKey);
+ keyState.put(newKey, controlKey);
}
/**
- * Get the Control Key in charge of a key press
- * @param key key pressed (String value of KeyCode)
- * @return the Control Key behaviour of the key pressed.
+ * Persistently saves the keybindings the user has set.
*/
- public ControlKey getKey(String key){
- return keyState.get(key);
+ public void save(){
+ try {
+ // open the filestream and write to it
+ FileOutputStream fos = new FileOutputStream(
+ System.getProperty("user.dir")+
+ "/settings/keyBindings.xml");
+ XMLEncoder xmlEncoder = new XMLEncoder(fos);
+ xmlEncoder.writeObject(this.keyState);
+ xmlEncoder.close();
+ } catch (FileNotFoundException e) {
+ e.printStackTrace();
+ }
+ }
+
+ /**
+ * Loads the persistently saved keybindings the user has set.
+ */
+ public void load(){
+ try {
+ // access settings folder, create if it doesn't exist
+ File settingsFolder = new File(
+ System.getProperty("user.dir")+"/settings");
+ if (!settingsFolder.exists()){
+ settingsFolder.mkdir();
+ }
+
+ // access keybindings xml file, create if it doesn't exist
+ File savedFile = new File(
+ settingsFolder+"/keyBindings.xml");
+ if (!savedFile.exists()){
+ savedFile.createNewFile();
+ FileOutputStream fos = new FileOutputStream(savedFile);
+ XMLEncoder xmlEncoder = new XMLEncoder(fos);
+ xmlEncoder.writeObject(this.keyState);
+ xmlEncoder.close();
+ }
+
+ // load the saved settings into the game
+ InputStream is = new FileInputStream(savedFile);
+ XMLDecoder xmlDecoder = new XMLDecoder(is);
+ Map savedKeyState
+ = (Map)xmlDecoder.readObject();
+ xmlDecoder.close();
+ this.keyState = savedKeyState;
+
+ } catch (FileNotFoundException e) {
+ e.printStackTrace();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
}
}
diff --git a/racevisionGame/src/main/java/visualiser/gameController/Keys/SailsToggleKey.java b/racevisionGame/src/main/java/visualiser/gameController/Keys/SailsToggleKey.java
index f1e4d65b..a04b7d77 100644
--- a/racevisionGame/src/main/java/visualiser/gameController/Keys/SailsToggleKey.java
+++ b/racevisionGame/src/main/java/visualiser/gameController/Keys/SailsToggleKey.java
@@ -10,11 +10,9 @@ public class SailsToggleKey extends ControlKey {
/**
* Constructor for Control
- * @param name name of the key
- *
*/
- public SailsToggleKey(String name) {
- super(name, BoatActionEnum.NOT_A_STATUS);
+ public SailsToggleKey() {
+ super("Toggle Sails", BoatActionEnum.NOT_A_STATUS);
}
/**
diff --git a/racevisionGame/src/main/java/visualiser/gameController/Keys/TackGybeKey.java b/racevisionGame/src/main/java/visualiser/gameController/Keys/TackGybeKey.java
index 80252e73..5c164443 100644
--- a/racevisionGame/src/main/java/visualiser/gameController/Keys/TackGybeKey.java
+++ b/racevisionGame/src/main/java/visualiser/gameController/Keys/TackGybeKey.java
@@ -9,11 +9,9 @@ public class TackGybeKey extends ControlKey {
/**
* Constructor for Control
- * @param name name of the key
- *
*/
- public TackGybeKey(String name) {
- super(name, BoatActionEnum.TACK_GYBE);
+ public TackGybeKey() {
+ super("Tack/Gybe", BoatActionEnum.TACK_GYBE);
}
@Override
diff --git a/racevisionGame/src/main/java/visualiser/gameController/Keys/UpWindKey.java b/racevisionGame/src/main/java/visualiser/gameController/Keys/UpWindKey.java
index 333e5f1f..b4bbf489 100644
--- a/racevisionGame/src/main/java/visualiser/gameController/Keys/UpWindKey.java
+++ b/racevisionGame/src/main/java/visualiser/gameController/Keys/UpWindKey.java
@@ -9,11 +9,9 @@ public class UpWindKey extends ControlKey {
/**
* Constructor for Control
- * @param name name of the key
- *
*/
- public UpWindKey(String name) {
- super(name, BoatActionEnum.UPWIND);
+ public UpWindKey() {
+ super("Upwind", BoatActionEnum.UPWIND);
}
@Override
diff --git a/racevisionGame/src/main/java/visualiser/gameController/Keys/VMGKey.java b/racevisionGame/src/main/java/visualiser/gameController/Keys/VMGKey.java
index c01658bb..2c7237f0 100644
--- a/racevisionGame/src/main/java/visualiser/gameController/Keys/VMGKey.java
+++ b/racevisionGame/src/main/java/visualiser/gameController/Keys/VMGKey.java
@@ -1,6 +1,5 @@
package visualiser.gameController.Keys;
-import javafx.scene.input.KeyCode;
import network.Messages.Enums.BoatActionEnum;
/**
@@ -10,11 +9,9 @@ public class VMGKey extends ControlKey{
/**
* Constructor for Control
- *
- * @param name name of the key
*/
- public VMGKey(String name) {
- super(name, BoatActionEnum.AUTO_PILOT);
+ public VMGKey() {
+ super("VMG", BoatActionEnum.AUTO_PILOT);
}
@Override
diff --git a/racevisionGame/src/main/java/visualiser/gameController/Keys/ZoomInKey.java b/racevisionGame/src/main/java/visualiser/gameController/Keys/ZoomInKey.java
index 51f98a58..e9a8ad7b 100644
--- a/racevisionGame/src/main/java/visualiser/gameController/Keys/ZoomInKey.java
+++ b/racevisionGame/src/main/java/visualiser/gameController/Keys/ZoomInKey.java
@@ -5,8 +5,8 @@ package visualiser.gameController.Keys;
*/
public class ZoomInKey extends ControlKey {
- public ZoomInKey(String name) {
- super(name);
+ public ZoomInKey() {
+ super("Zoom In");
}
@Override
diff --git a/racevisionGame/src/main/java/visualiser/gameController/Keys/ZoomOutKey.java b/racevisionGame/src/main/java/visualiser/gameController/Keys/ZoomOutKey.java
index 6da2210c..cbba97fc 100644
--- a/racevisionGame/src/main/java/visualiser/gameController/Keys/ZoomOutKey.java
+++ b/racevisionGame/src/main/java/visualiser/gameController/Keys/ZoomOutKey.java
@@ -7,11 +7,9 @@ public class ZoomOutKey extends ControlKey{
/**
* Constructor for Control
- * @param name name of the key
- *
*/
- public ZoomOutKey(String name) {
- super(name);
+ public ZoomOutKey() {
+ super("Zoom Out");
}
@Override
diff --git a/racevisionGame/src/main/java/visualiser/layout/Subject3D.java b/racevisionGame/src/main/java/visualiser/layout/Subject3D.java
new file mode 100644
index 00000000..af76f4f4
--- /dev/null
+++ b/racevisionGame/src/main/java/visualiser/layout/Subject3D.java
@@ -0,0 +1,65 @@
+package visualiser.layout;
+
+import javafx.scene.shape.Shape3D;
+import javafx.scene.transform.Rotate;
+import javafx.scene.transform.Translate;
+
+/**
+ * Wrapper for controlling the position and heading of rendered 3D models.
+ */
+public class Subject3D {
+ /**
+ * Rendered mesh
+ */
+ private Shape3D mesh;
+
+ /**
+ * Position translation updated by state listeners
+ */
+ private Translate position;
+
+ /**
+ * Heading rotation updated by state listeners
+ */
+ private Rotate heading;
+
+ /**
+ * Constructor for view subject wrapper
+ * @param mesh to be rendered
+ */
+ public Subject3D(Shape3D mesh) {
+ this.mesh = mesh;
+ this.position = new Translate();
+ this.heading = new Rotate(0, Rotate.Y_AXIS);
+
+ this.mesh.getTransforms().addAll(position, heading, new Rotate(90, Rotate.X_AXIS), new Rotate(180, Rotate.Y_AXIS));
+ }
+
+ public Shape3D getMesh() {
+ return mesh;
+ }
+
+ public Translate getPosition() {
+ return this.position;
+ }
+
+ public Rotate getHeading() {
+ return heading;
+ }
+
+ public void setX(double x) {
+ position.setX(x);
+ }
+
+ public void setY(double y) {
+ position.setY(y);
+ }
+
+ public void setZ(double z) {
+ position.setZ(z);
+ }
+
+ public void setHeading(double angle) {
+ heading.setAngle(angle);
+ }
+}
diff --git a/racevisionGame/src/main/java/visualiser/layout/View3D.java b/racevisionGame/src/main/java/visualiser/layout/View3D.java
new file mode 100644
index 00000000..27fe6086
--- /dev/null
+++ b/racevisionGame/src/main/java/visualiser/layout/View3D.java
@@ -0,0 +1,260 @@
+package visualiser.layout;
+
+import javafx.beans.value.ChangeListener;
+import javafx.collections.ListChangeListener;
+import javafx.collections.ObservableList;
+import javafx.scene.Group;
+import javafx.scene.PerspectiveCamera;
+import javafx.scene.SubScene;
+import javafx.scene.input.PickResult;
+import javafx.scene.layout.Pane;
+import javafx.scene.paint.Color;
+import javafx.scene.shape.Shape3D;
+import javafx.scene.transform.Rotate;
+import javafx.scene.transform.Translate;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Control for rendering 3D objects visible through a PerspectiveCamera. Implements Adapter Pattern to
+ * interface with camera, and allows clients to add shapes to the scene. All scenes contain sea plane and
+ * sky box, whose textures are set with special methods.
+ */
+public class View3D extends Pane {
+ /**
+ * Container for group and camera
+ */
+ private SubScene scene;
+ /**
+ * Observable list of renderable items
+ */
+ private ObservableList items;
+ /**
+ * Map for selecting Subject3D from Shape3D
+ */
+ private Map selectionMap;
+ /**
+ * Subject tracked by camera
+ */
+ private Subject3D target;
+ /**
+ * Rendering container for shapes
+ */
+ private Group world;
+ /**
+ * Near limit of view frustum
+ */
+ private double nearClip;
+ /**
+ * Far limit of view frustum
+ */
+ private double farClip;
+ /**
+ * Camera origin
+ */
+ private Translate pivot;
+ /**
+ * Distance of camera from pivot point
+ */
+ private Translate distance;
+ /**
+ * Angle along ground between z-axis and camera
+ */
+ private Rotate yaw;
+ /**
+ * Angle between ground plane and camera direction
+ */
+ private Rotate pitch;
+ /**
+ * Single listener for subject heading changes
+ */
+ private ChangeListener super Number> pivotHeading = (o, prev, curr) -> yaw.setAngle((double)curr);
+ /**
+ * Single listener for subject position (x) changes
+ */
+ private ChangeListener super Number> pivotX = (o, prev, curr) -> pivot.setX((double)curr);
+ /**
+ * Single listener for subject position (y) changes
+ */
+ private ChangeListener super Number> pivotY = (o, prev, curr) -> pivot.setY((double)curr);
+ /**
+ * Single listener for subject position (z) changes
+ */
+ private ChangeListener super Number> pivotZ = (o, prev, curr) -> pivot.setZ((double)curr);
+ /**
+ * Distance to switch from third person to bird's eye
+ */
+ private double THIRD_PERSON_LIMIT = 100;
+
+ /**
+ * Default constructor for View3D. Sets up Scene and PerspectiveCamera.
+ */
+ public View3D() {
+ this.world = new Group();
+ this.selectionMap = new HashMap<>();
+ this.target = null;
+ this.scene = new SubScene(world, 300, 300);
+
+ scene.widthProperty().bind(this.widthProperty());
+ scene.heightProperty().bind(this.heightProperty());
+ scene.setFill(new Color(0.2, 0.6, 1, 1));
+
+ scene.setCamera(buildCamera());
+
+ this.getChildren().add(scene);
+ }
+
+ /**
+ * Sets up camera view frustum and binds transformations
+ * @return perspective camera
+ */
+ private PerspectiveCamera buildCamera() {
+ PerspectiveCamera camera = new PerspectiveCamera(true);
+
+ // Set up view frustum
+ nearClip = 0.1;
+ farClip = 3000.0;
+ camera.setNearClip(nearClip);
+ camera.setFarClip(farClip);
+
+ // Set up transformations
+ pivot = new Translate();
+ distance = new Translate();
+ yaw = new Rotate(0, Rotate.Y_AXIS);
+ pitch = new Rotate(0, Rotate.X_AXIS);
+ camera.getTransforms().addAll(pivot, yaw, pitch, distance);
+
+ return camera;
+ }
+
+ /**
+ * Provide the list of subjects to be automatically added or removed from the view as the list
+ * changes.
+ * @param items list managed by client
+ */
+ public void setItems(ObservableList items) {
+ this.items = items;
+ this.items.addListener((ListChangeListener super Subject3D>) c -> {
+ while(c.next()) {
+ if (c.wasRemoved() || c.wasAdded()) {
+ for (Subject3D shape : c.getRemoved()) {
+ world.getChildren().remove(shape.getMesh());
+ selectionMap.remove(shape.getMesh());
+ }
+ for (Subject3D shape : c.getAddedSubList()) {
+ world.getChildren().add(shape.getMesh());
+ selectionMap.put(shape.getMesh(), shape);
+ }
+ }
+ }
+ });
+ }
+
+ /**
+ * Intercept mouse clicks on subjects in view. The applied listener cannot be removed.
+ */
+ public void enableTracking() {
+ scene.setOnMousePressed(e -> {
+ PickResult result = e.getPickResult();
+ if(result != null && result.getIntersectedNode() != null && result.getIntersectedNode() instanceof Shape3D) {
+ trackSubject(selectionMap.get(result.getIntersectedNode()));
+ }
+ });
+ }
+
+ /**
+ * Stop camera from following the last selected subject
+ */
+ private void untrackSubject() {
+ if(target != null) {
+ target.getPosition().xProperty().removeListener(pivotX);
+ target.getPosition().yProperty().removeListener(pivotY);
+ target.getPosition().zProperty().removeListener(pivotZ);
+ target.getHeading().angleProperty().removeListener(pivotHeading);
+ }
+ }
+
+ /**
+ * Set camera to follow the selected subject
+ * @param subject to track
+ */
+ private void trackSubject(Subject3D subject) {
+ untrackSubject();
+ target = subject;
+
+ updatePivot(target.getPosition());
+ setYaw(target.getHeading().getAngle());
+
+ target.getPosition().xProperty().addListener(pivotX);
+ target.getPosition().yProperty().addListener(pivotY);
+ target.getPosition().zProperty().addListener(pivotZ);
+ target.getHeading().angleProperty().addListener(pivotHeading);
+
+ this.setDistance(THIRD_PERSON_LIMIT);
+ this.setPitch(20);
+ }
+
+ public void setNearClip(double nearClip) {
+ this.nearClip = nearClip;
+ }
+
+ public void setFarClip(double farClip) {
+ this.farClip = farClip;
+ }
+
+ /**
+ * Sets the coordinates of the camera pivot once.
+ * @param pivot source of coordinates
+ */
+ public void updatePivot(Translate pivot) {
+ this.pivot.setX(pivot.getX());
+ this.pivot.setY(pivot.getY());
+ this.pivot.setZ(pivot.getZ());
+ }
+
+ /**
+ * Set distance of camera from pivot
+ * @param distance in units
+ */
+ public void setDistance(double distance) {
+ this.distance.setZ(-distance);
+ }
+
+ /**
+ * Adds delta to current distance and changes camera mode if applicable.
+ * Third person limit specifies the distance at which a third person camera
+ * switches to bird's-eye, remaining focused on the same position.
+ * @param delta amount to change distance by
+ */
+ public void updateDistance(double delta) {
+ double distance = -this.distance.getZ() + delta;
+
+ if(distance <= 0) {
+ this.setDistance(0);
+ } else if(distance > THIRD_PERSON_LIMIT) {
+ untrackSubject();
+ this.setYaw(0);
+ this.setPitch(60);
+ this.setDistance(distance);
+ } else {
+ this.setDistance(distance);
+ }
+ }
+
+ /**
+ * Set angle of camera from z-axis along ground
+ * @param yaw in degrees
+ */
+ public void setYaw(double yaw) {
+ this.yaw.setAngle(yaw);
+ }
+
+ /**
+ * Set elevation of camera
+ * @param pitch in degrees
+ */
+ public void setPitch(double pitch) {
+ this.pitch.setAngle(-pitch);
+ }
+}
diff --git a/racevisionGame/src/main/java/visualiser/model/ResizableRaceCanvas.java b/racevisionGame/src/main/java/visualiser/model/ResizableRaceCanvas.java
index fc6a0858..03e86e27 100644
--- a/racevisionGame/src/main/java/visualiser/model/ResizableRaceCanvas.java
+++ b/racevisionGame/src/main/java/visualiser/model/ResizableRaceCanvas.java
@@ -39,9 +39,9 @@ public class ResizableRaceCanvas extends ResizableCanvas {
private Image sailsLuff = new Image("/images/sailsLuff.gif", 25, 10, false, false);
/**
- * The race we read data from and draw.
+ * The race state we read data from and draw.
*/
- private VisualiserRaceEvent visualiserRace;
+ private VisualiserRaceState raceState;
private boolean annoName = true;
@@ -56,14 +56,14 @@ public class ResizableRaceCanvas extends ResizableCanvas {
/**
* Constructs a {@link ResizableRaceCanvas} using a given {@link VisualiserRaceEvent}.
- * @param visualiserRace The race that data is read from in order to be drawn.
+ * @param raceState The race state to be drawn.
*/
- public ResizableRaceCanvas(VisualiserRaceEvent visualiserRace) {
+ public ResizableRaceCanvas(VisualiserRaceState raceState) {
super();
- this.visualiserRace = visualiserRace;
+ this.raceState = raceState;
- RaceDataSource raceData = visualiserRace.getVisualiserRaceState().getRaceDataSource();
+ RaceDataSource raceData = raceState.getRaceDataSource();
double lat1 = raceData.getMapTopLeft().getLatitude();
double long1 = raceData.getMapTopLeft().getLongitude();
@@ -276,8 +276,8 @@ public class ResizableRaceCanvas extends ResizableCanvas {
boat.getCountry(),
boat.getCurrentSpeed(),
this.map.convertGPS(boat.getPosition()),
- boat.getTimeToNextMarkFormatted(this.visualiserRace.getVisualiserRaceState().getRaceClock().getCurrentTime()),
- boat.getTimeSinceLastMarkFormatted(this.visualiserRace.getVisualiserRaceState().getRaceClock().getCurrentTime()),
+ boat.getTimeToNextMarkFormatted(raceState.getRaceClock().getCurrentTime()),
+ boat.getTimeSinceLastMarkFormatted(raceState.getRaceClock().getCurrentTime()),
Color.BLACK,
20 );
@@ -291,7 +291,7 @@ public class ResizableRaceCanvas extends ResizableCanvas {
*/
private void drawBoats() {
- List boats = new ArrayList<>(visualiserRace.getVisualiserRaceState().getBoats());
+ List boats = new ArrayList<>(raceState.getBoats());
//Sort to ensure we draw boats in consistent order.
boats.sort(Comparator.comparingInt(Boat::getSourceID));
@@ -510,7 +510,7 @@ public class ResizableRaceCanvas extends ResizableCanvas {
*/
private void drawMarks() {
- for (Mark mark : new ArrayList<>(visualiserRace.getVisualiserRaceState().getMarks())) {
+ for (Mark mark : new ArrayList<>(raceState.getMarks())) {
drawMark(mark);
}
}
@@ -574,7 +574,7 @@ public class ResizableRaceCanvas extends ResizableCanvas {
//Calculate the screen coordinates of the boundary.
- List boundary = new ArrayList<>(visualiserRace.getVisualiserRaceState().getBoundary());
+ List boundary = new ArrayList<>(raceState.getBoundary());
double[] xpoints = new double[boundary.size()];
double[] ypoints = new double[boundary.size()];
@@ -601,8 +601,8 @@ public class ResizableRaceCanvas extends ResizableCanvas {
public void drawRace() {
//Update RaceMap with new GPS values of race.
- this.map.setGPSTopLeft(visualiserRace.getVisualiserRaceState().getRaceDataSource().getMapTopLeft());
- this.map.setGPSBotRight(visualiserRace.getVisualiserRaceState().getRaceDataSource().getMapBottomRight());
+ this.map.setGPSTopLeft(raceState.getRaceDataSource().getMapTopLeft());
+ this.map.setGPSBotRight(raceState.getRaceDataSource().getMapBottomRight());
clear();
@@ -627,7 +627,7 @@ public class ResizableRaceCanvas extends ResizableCanvas {
* draws a transparent line around the course that shows the paths boats must travel
*/
public void drawRaceLine(){
- List legs = this.visualiserRace.getVisualiserRaceState().getLegs();
+ List legs = raceState.getLegs();
GPSCoordinate legStartPoint = legs.get(0).getStartCompoundMark().getAverageGPSCoordinate();
GPSCoordinate nextStartPoint;
for (int i = 0; i < legs.size() -1; i++) {
diff --git a/racevisionGame/src/main/java/visualiser/model/VisualiserBoat.java b/racevisionGame/src/main/java/visualiser/model/VisualiserBoat.java
index 6cbfdaa3..555def15 100644
--- a/racevisionGame/src/main/java/visualiser/model/VisualiserBoat.java
+++ b/racevisionGame/src/main/java/visualiser/model/VisualiserBoat.java
@@ -1,11 +1,10 @@
package visualiser.model;
+import javafx.beans.property.ObjectProperty;
+import javafx.beans.property.SimpleObjectProperty;
import javafx.scene.paint.Color;
import network.Messages.Enums.BoatStatusEnum;
-import shared.model.Azimuth;
-import shared.model.Boat;
-import shared.model.Constants;
-import shared.model.GPSCoordinate;
+import shared.model.*;
import java.time.Duration;
import java.time.ZonedDateTime;
@@ -61,7 +60,8 @@ public class VisualiserBoat extends Boat {
private boolean isClientBoat = false;
-
+ private ObjectProperty positionProperty;
+ private ObjectProperty bearingProperty;
/**
@@ -239,4 +239,38 @@ public class VisualiserBoat extends Boat {
public void setClientBoat(boolean clientBoat) {
isClientBoat = clientBoat;
}
+
+ @Override
+ public GPSCoordinate getPosition() {
+ return positionProperty.get();
+ }
+
+ @Override
+ public void setPosition(GPSCoordinate position) {
+ if(this.positionProperty == null) {
+ this.positionProperty = new SimpleObjectProperty<>();
+ }
+ this.positionProperty.set(position);
+ }
+
+ public ObjectProperty positionProperty() {
+ return positionProperty;
+ }
+
+ @Override
+ public Bearing getBearing() {
+ return bearingProperty.get();
+ }
+
+ @Override
+ public void setBearing(Bearing bearing) {
+ if(this.bearingProperty == null) {
+ this.bearingProperty = new SimpleObjectProperty<>();
+ }
+ this.bearingProperty.set(bearing);
+ }
+
+ public ObjectProperty bearingProperty() {
+ return bearingProperty;
+ }
}
diff --git a/racevisionGame/src/main/java/visualiser/utils/GPSConverter.java b/racevisionGame/src/main/java/visualiser/utils/GPSConverter.java
new file mode 100644
index 00000000..22dd937f
--- /dev/null
+++ b/racevisionGame/src/main/java/visualiser/utils/GPSConverter.java
@@ -0,0 +1,104 @@
+package visualiser.utils;
+
+import shared.dataInput.RaceDataSource;
+import shared.model.GPSCoordinate;
+import visualiser.model.GraphCoordinate;
+
+/**
+ * Converts GPS coordinates to view volume coordinates. Longitudes are equally spaced at all latitudes,
+ * which leads to inaccurate distance measurements close to the poles. This is acceptable as races are
+ * not likely to be set there.
+ */
+public class GPSConverter {
+ private double longRight;
+ private double longLeft;
+ private double latBottom;
+ private double latTop;
+ /**
+ * Conversion factor from longitude to view units
+ */
+ private double longitudeFactor;
+ /**
+ * Conversion factor from latitude to view units
+ */
+ private double latitudeFactor;
+
+ /**
+ * Set up projection with default view boundaries from RaceDataSource
+ * @param source for view boundaries
+ * @param longitudeFactor separation of a degree of longitude in view units
+ * @param latitudeFactor separation of a degree of latitude in view units
+ */
+ public GPSConverter(RaceDataSource source, double longitudeFactor, double latitudeFactor) {
+ this.latTop = source.getMapTopLeft().getLatitude();
+ this.longLeft = source.getMapTopLeft().getLongitude();
+ this.latBottom = source.getMapBottomRight().getLatitude();
+ this.longRight = source.getMapBottomRight().getLongitude();
+ this.longitudeFactor = longitudeFactor;
+ this.latitudeFactor = latitudeFactor;
+ }
+
+ /**
+ * Converts GPS coordinates to coordinates for container.
+ * It is assumed that the provided GPSCoordinate will always be within the GPSCoordinate boundaries of the RaceMap.
+ *
+ * @param lat GPS latitude
+ * @param lon GPS longitude
+ * @return GraphCoordinate (pair of doubles)
+ * @see GraphCoordinate
+ */
+ private GraphCoordinate convertGPS(double lat, double lon) {
+
+ //Calculate the width/height, in gps coordinates, of the map.
+ double longWidth = longRight - longLeft;
+ double latHeight = latBottom - latTop;
+
+ //Calculate the distance between the specified coordinate and the edge of the map.
+ double longDelta = lon - longLeft;
+ double latDelta = lat - latTop;
+
+ //Calculate the proportion along horizontally, from the left, the coordinate should be.
+ double longProportion = longDelta / longWidth;
+ //Calculate the proportion along vertically, from the top, the coordinate should be.
+ double latProportion = latDelta / latHeight;
+
+ //Check which metric dimension of our map is smaller. We use this to ensure that any rendered stuff retains its correct aspect ratio, and that everything is visible on screen.
+ double smallerDimension = Math.min(longitudeFactor, latitudeFactor);
+
+ //Calculate the x and y pixel coordinates.
+ //We take the complement of latProportion to flip it.
+ int x = (int) (longProportion * smallerDimension);
+ int y = (int) (latProportion * smallerDimension);
+
+ //Because we try to maintain the correct aspect ratio, we will end up with "spare" pixels along the larger dimension (e.g., width 800, height 600, 200 extra pixels along width).
+ double extraDistance = Math.abs(longitudeFactor - latitudeFactor);
+ //We therefore "center" the coordinates along this larger dimension, by adding half of the extra pixels.
+ if (longitudeFactor > latitudeFactor) {
+ x += extraDistance / 2;
+ } else {
+ y += extraDistance / 2;
+ }
+
+
+ //Finally, create the GraphCoordinate.
+ GraphCoordinate graphCoordinate = new GraphCoordinate(x, y);
+
+
+ return graphCoordinate;
+
+ }
+
+ /**
+ * Converts the GPS Coordinate to GraphCoordinate.
+ * It is assumed that the provided GPSCoordinate will always be within the GPSCoordinate boundaries of the RaceMap.
+ *
+ * @param coordinate GPSCoordinate representation of Latitude and Longitude.
+ * @return GraphCoordinate that the GPS is coordinates are to be displayed on the map.
+ * @see GraphCoordinate
+ * @see GPSCoordinate
+ */
+ public GraphCoordinate convertGPS(GPSCoordinate coordinate) {
+ return convertGPS(coordinate.getLatitude(), coordinate.getLongitude());
+ }
+
+}
diff --git a/racevisionGame/src/main/resources/assets/V1.2 Complete Boat.stl b/racevisionGame/src/main/resources/assets/V1.2 Complete Boat.stl
new file mode 100644
index 00000000..a952ca77
Binary files /dev/null and b/racevisionGame/src/main/resources/assets/V1.2 Complete Boat.stl differ
diff --git a/racevisionGame/src/main/resources/css/keyBindings.css b/racevisionGame/src/main/resources/css/keyBindings.css
new file mode 100644
index 00000000..a572116e
--- /dev/null
+++ b/racevisionGame/src/main/resources/css/keyBindings.css
@@ -0,0 +1,45 @@
+.list-view .list-cell {
+ -fx-cell-size: 40;
+ -fx-font-family: "Tahoma";
+ -fx-background-color: #fffff0;
+ -fx-alignment: center;
+}
+
+.list-view .list-cell:even {
+ -fx-background-color: #ffffdc;
+}
+
+.list-view .list-cell:selected {
+ -fx-background-color: #f9e5c3;
+ -fx-font-family: "Comic Sans MS";
+ -fx-font-weight: bold;
+ -fx-font-size: 18;
+}
+
+.button {
+ -fx-background-color: linear-gradient(#acdeff 50%, #a9c8ff 100%);
+ -fx-background-radius: 4px;
+ -fx-border-radius: 4px;
+ -fx-text-fill: #242d35;
+ -fx-font-size: 12px;
+ -fx-font-family: "Courier New";
+ -fx-border-color: #a9c8ff;
+}
+
+.button:focused {
+ -fx-background-color: white;
+ -fx-border-color: #0056bd;
+}
+
+#anchor {
+ -fx-background-color: #fdfac3;
+}
+#menu{
+ -fx-background-color: linear-gradient(#acdeff 50%, #a9c8ff 100%);
+ -fx-background-radius: 4px;
+ -fx-border-radius: 4px;
+ -fx-text-fill: #242d35;
+ -fx-font-size: 12px;
+ -fx-font-family: "Verdana";
+ -fx-border-color: #a9c8ff;
+}
\ No newline at end of file
diff --git a/racevisionGame/src/main/resources/mock/mockXML/boatTest.xml b/racevisionGame/src/main/resources/mock/mockXML/boatTest.xml
index 9295dc07..59e2f79d 100644
--- a/racevisionGame/src/main/resources/mock/mockXML/boatTest.xml
+++ b/racevisionGame/src/main/resources/mock/mockXML/boatTest.xml
@@ -32,7 +32,6 @@
-
diff --git a/racevisionGame/src/main/resources/mock/mockXML/raceSixPlayers.xml b/racevisionGame/src/main/resources/mock/mockXML/raceSixPlayers.xml
index ad14d931..6ae04ef8 100644
--- a/racevisionGame/src/main/resources/mock/mockXML/raceSixPlayers.xml
+++ b/racevisionGame/src/main/resources/mock/mockXML/raceSixPlayers.xml
@@ -5,12 +5,7 @@
RACE_CREATION_TIME
-
-
-
-
-
-
+
diff --git a/racevisionGame/src/main/resources/mock/mockXML/schema/boatsSchema.xsd b/racevisionGame/src/main/resources/mock/mockXML/schema/boatsSchema.xsd
new file mode 100644
index 00000000..6d5af617
--- /dev/null
+++ b/racevisionGame/src/main/resources/mock/mockXML/schema/boatsSchema.xsd
@@ -0,0 +1,36 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/racevisionGame/src/main/resources/mock/mockXML/schema/regattaSchema.xsd b/racevisionGame/src/main/resources/mock/mockXML/schema/regattaSchema.xsd
new file mode 100644
index 00000000..5c9fb774
--- /dev/null
+++ b/racevisionGame/src/main/resources/mock/mockXML/schema/regattaSchema.xsd
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/racevisionGame/src/main/resources/visualiser/images/arrow.png b/racevisionGame/src/main/resources/visualiser/images/arrow.png
index fab6e21d..cd7bab10 100644
Binary files a/racevisionGame/src/main/resources/visualiser/images/arrow.png and b/racevisionGame/src/main/resources/visualiser/images/arrow.png differ
diff --git a/racevisionGame/src/main/resources/visualiser/images/lobby.gif b/racevisionGame/src/main/resources/visualiser/images/lobby.gif
new file mode 100644
index 00000000..c70d8df6
Binary files /dev/null and b/racevisionGame/src/main/resources/visualiser/images/lobby.gif differ
diff --git a/racevisionGame/src/main/resources/visualiser/scenes/keyBindings.fxml b/racevisionGame/src/main/resources/visualiser/scenes/keyBindings.fxml
new file mode 100644
index 00000000..40217567
--- /dev/null
+++ b/racevisionGame/src/main/resources/visualiser/scenes/keyBindings.fxml
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/racevisionGame/src/main/resources/visualiser/scenes/main.fxml b/racevisionGame/src/main/resources/visualiser/scenes/main.fxml
index 074a9b31..7aff44c0 100644
--- a/racevisionGame/src/main/resources/visualiser/scenes/main.fxml
+++ b/racevisionGame/src/main/resources/visualiser/scenes/main.fxml
@@ -7,7 +7,7 @@
-
+
diff --git a/racevisionGame/src/main/resources/visualiser/scenes/notification.fxml b/racevisionGame/src/main/resources/visualiser/scenes/notification.fxml
new file mode 100644
index 00000000..0e4a7c5b
--- /dev/null
+++ b/racevisionGame/src/main/resources/visualiser/scenes/notification.fxml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/racevisionGame/src/main/resources/visualiser/scenes/race.fxml b/racevisionGame/src/main/resources/visualiser/scenes/race.fxml
index bbc1c077..b6496743 100644
--- a/racevisionGame/src/main/resources/visualiser/scenes/race.fxml
+++ b/racevisionGame/src/main/resources/visualiser/scenes/race.fxml
@@ -22,7 +22,7 @@
-
+
diff --git a/racevisionGame/src/main/resources/visualiser/scenes/title.fxml b/racevisionGame/src/main/resources/visualiser/scenes/title.fxml
index f448a226..255381af 100644
--- a/racevisionGame/src/main/resources/visualiser/scenes/title.fxml
+++ b/racevisionGame/src/main/resources/visualiser/scenes/title.fxml
@@ -1,61 +1,61 @@
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/racevisionGame/src/test/java/mock/model/ConstantWindGeneratorTest.java b/racevisionGame/src/test/java/mock/model/ConstantWindGeneratorTest.java
index 6f67dc30..f7c69acf 100644
--- a/racevisionGame/src/test/java/mock/model/ConstantWindGeneratorTest.java
+++ b/racevisionGame/src/test/java/mock/model/ConstantWindGeneratorTest.java
@@ -1,5 +1,7 @@
package mock.model;
+import mock.model.wind.ConstantWindGenerator;
+import mock.model.wind.WindGenerator;
import org.junit.Before;
import org.junit.Test;
import shared.model.Bearing;
diff --git a/racevisionGame/src/test/java/mock/model/MockBoatTest.java b/racevisionGame/src/test/java/mock/model/MockBoatTest.java
index 8d1f45ee..98b4ca27 100644
--- a/racevisionGame/src/test/java/mock/model/MockBoatTest.java
+++ b/racevisionGame/src/test/java/mock/model/MockBoatTest.java
@@ -3,6 +3,7 @@ package mock.model;
import org.junit.Before;
import org.junit.Test;
import shared.model.Bearing;
+import shared.model.Boat;
import shared.model.GPSCoordinate;
import shared.model.Mark;
@@ -13,6 +14,13 @@ public class MockBoatTest {
private Mark near;
private Mark far;
+
+ public static MockBoat createMockBoat() {
+ Boat boat = new Boat(121, "Test boat", "TS");
+ MockBoat mockBoat = new MockBoat(boat, null);
+ return mockBoat;
+ }
+
@Before
public void setUp() {
boat = new MockBoat(0, "Bob", "NZ", null);
diff --git a/racevisionGame/src/test/java/mock/model/MockRaceTest.java b/racevisionGame/src/test/java/mock/model/MockRaceTest.java
index 4f3f7705..f53b2970 100644
--- a/racevisionGame/src/test/java/mock/model/MockRaceTest.java
+++ b/racevisionGame/src/test/java/mock/model/MockRaceTest.java
@@ -1,7 +1,8 @@
package mock.model;
import mock.dataInput.PolarParserTest;
-import network.Messages.LatestMessages;
+import mock.model.wind.ConstantWindGenerator;
+import mock.model.wind.WindGenerator;
import shared.dataInput.*;
import shared.exceptions.InvalidBoatDataException;
import shared.exceptions.InvalidRaceDataException;
@@ -9,8 +10,6 @@ import shared.exceptions.InvalidRegattaDataException;
import shared.model.Bearing;
import shared.model.Constants;
-import static org.junit.Assert.*;
-
public class MockRaceTest {
//TODO
diff --git a/racevisionGame/src/test/java/mock/model/NewPolarsTest.java b/racevisionGame/src/test/java/mock/model/NewPolarsTest.java
new file mode 100644
index 00000000..dcbcc6cc
--- /dev/null
+++ b/racevisionGame/src/test/java/mock/model/NewPolarsTest.java
@@ -0,0 +1,141 @@
+package mock.model;
+
+import mock.dataInput.PolarParser;
+import org.junit.Before;
+import org.junit.Test;
+import shared.model.Bearing;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeMap;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.*;
+
+/**
+ * Created by fwy13 on 5/09/17.
+ */
+public class NewPolarsTest {
+
+ @Before
+ public void setUp(){
+ PolarParser.parseNewPolars("mock/polars/acc_polars.csv");
+ NewPolars.linearInterpolatePolars();
+
+
+// Uncomment if you want to read the linear interpolation in text
+// Method getPolars = null;
+// try {
+// getPolars = NewPolars.class.getDeclaredMethod("printOutLinearInterpolated");
+// } catch (NoSuchMethodException e) {
+// e.printStackTrace();
+// }
+// getPolars.setAccessible(true);
+// try {
+// getPolars.invoke(NewPolars.newPolars);
+// } catch (IllegalAccessException e) {
+// e.printStackTrace();
+// } catch (InvocationTargetException e) {
+// e.printStackTrace();
+// }
+ }
+
+ @Test
+ public void testQuads() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
+ //reflection for private class
+ Class[] parameterTypes = new Class[1];
+ parameterTypes[0] = Double.TYPE;
+ Method getQuads = NewPolars.class.getDeclaredMethod("getQuadrant", parameterTypes);
+ getQuads.setAccessible(true);
+
+ //start invoking
+ Object[] paras1 = new Object[1];
+ paras1[0] = (new Double(0)).doubleValue();
+ int q1 = (int) getQuads.invoke(NewPolars.newPolars, paras1);
+ assertEquals(q1, 1);
+
+ //start invoking
+ Object[] paras2 = new Object[1];
+ paras2[0] = (new Double(90)).doubleValue();
+ int q2 = (int) getQuads.invoke(NewPolars.newPolars, paras2);
+ assertEquals(q2, 2);
+
+ //start invoking
+ Object[] paras3 = new Object[1];
+ paras3[0] = (new Double(180)).doubleValue();
+ int q3 = (int) getQuads.invoke(NewPolars.newPolars, paras3);
+ assertEquals(q3, 3);
+
+ //start invoking
+ Object[] paras4 = new Object[1];
+ paras4[0] = (new Double(270)).doubleValue();
+ int q4 = (int) getQuads.invoke(NewPolars.newPolars, paras4);
+ assertEquals(q4, 4);
+
+ //start invoking
+ Object[] paras5 = new Object[1];
+ paras5[0] = (new Double(360)).doubleValue();
+ int q5 = (int) getQuads.invoke(NewPolars.newPolars, paras5);
+ assertEquals(q5, 1);
+
+ }
+
+ @Test
+ public void testEdgeSpeeds(){
+ //just make sure that speeds at certain angles do not throw a null exception and are not negative
+ double maxTWS = 30;
+
+ for (double tws = 0; tws < maxTWS; tws += 1){
+ for (double j = 0; j < 360; j++){
+ Bearing twa = Bearing.fromDegrees(j);
+ for (double i = 0; i < 360; i++){
+ Bearing boatBearing = Bearing.fromDegrees(i);
+ double speed = NewPolars.calculateSpeed(twa, tws, boatBearing);
+ assertTrue(speed >= 0);
+ }
+ }
+ }
+
+ }
+
+ @Test
+ public void testClosestSpeeds() throws NoSuchMethodException, NoSuchFieldException, InvocationTargetException, IllegalAccessException {
+ //reflection for private class
+ Method getClosest = NewPolars.class.getDeclaredMethod("getClosest", double.class, Set.class);
+ getClosest.setAccessible(true);
+
+ Method getPolars = NewPolars.class.getDeclaredMethod("getPolars");
+ getPolars.setAccessible(true);
+
+ double maxTWS = 30;
+
+ //only catches for nulls
+ for (double tws = 0; tws < maxTWS; tws += 1){
+ Map> polars = (Map>) getPolars.invoke(NewPolars.newPolars);
+ double speed = (double) getClosest.invoke(NewPolars.newPolars, tws, polars.keySet());
+ assertTrue(speed >= 0);
+ }
+ }
+
+ @Test
+ public void testAutoVSCalculated(){
+ //test that the auto chosen speed is the same speed that is calculated
+ double maxTWS = 30;
+ for (double tws = 0; tws < maxTWS; tws ++){
+ for (double twa = 0; twa < 360; twa ++){
+ Bearing TW = Bearing.fromDegrees(twa);
+ for (double ba = 0; ba < 360; ba ++){
+ Bearing boatBearing = Bearing.fromDegrees(ba);
+ VMG autoVMG = NewPolars.setBestVMG(TW, tws, boatBearing);
+ double speed = NewPolars.calculateSpeed(TW, tws, autoVMG.getBearing());
+ assertTrue(autoVMG.getSpeed() == speed);
+ }
+ }
+ }
+ }
+
+}
diff --git a/racevisionGame/src/test/java/mock/model/RandomWindGeneratorTest.java b/racevisionGame/src/test/java/mock/model/RandomWindGeneratorTest.java
index 76eed977..0f60bcea 100644
--- a/racevisionGame/src/test/java/mock/model/RandomWindGeneratorTest.java
+++ b/racevisionGame/src/test/java/mock/model/RandomWindGeneratorTest.java
@@ -1,5 +1,6 @@
package mock.model;
+import mock.model.wind.RandomWindGenerator;
import org.junit.Before;
import org.junit.Test;
import shared.model.Bearing;
diff --git a/racevisionGame/src/test/java/mock/model/SourceIdAllocatorTest.java b/racevisionGame/src/test/java/mock/model/SourceIdAllocatorTest.java
index 7240e01b..ad310540 100644
--- a/racevisionGame/src/test/java/mock/model/SourceIdAllocatorTest.java
+++ b/racevisionGame/src/test/java/mock/model/SourceIdAllocatorTest.java
@@ -1,6 +1,7 @@
package mock.model;
import mock.exceptions.SourceIDAllocationException;
+import network.Messages.Enums.RaceStatusEnum;
import org.junit.Before;
import org.junit.Test;
@@ -15,30 +16,16 @@ import static org.junit.Assert.*;
*/
public class SourceIdAllocatorTest {
- /**
- * This is the list of source IDs that we start with.
- */
- private List originalSourceIDs;
-
- /**
- * Used to allocate source IDs.
- */
+ private MockRace mockRace;
private SourceIdAllocator sourceIdAllocator;
@Before
public void setUp() throws Exception {
- originalSourceIDs = new ArrayList<>();
- originalSourceIDs.add(120);
- originalSourceIDs.add(121);
- originalSourceIDs.add(122);
- originalSourceIDs.add(123);
- originalSourceIDs.add(124);
- originalSourceIDs.add(125);
-
+ mockRace = MockRaceTest.createMockRace();
- sourceIdAllocator = new SourceIdAllocator(originalSourceIDs);
+ sourceIdAllocator = new SourceIdAllocator(mockRace);
}
@@ -49,11 +36,12 @@ public class SourceIdAllocatorTest {
@Test
public void emptyAllocationTest() {
- SourceIdAllocator allocator = new SourceIdAllocator(new ArrayList<>());
+ mockRace.getRaceDataSource().getParticipants().removeAll(mockRace.getBoatDataSource().getBoats().keySet());
+ mockRace.getRaceDataSource().getParticipants().addAll(mockRace.getBoatDataSource().getBoats().keySet());
try {
- int sourceID = allocator.allocateSourceID();
+ int sourceID = sourceIdAllocator.allocateSourceID();
fail("Exception should have been thrown, but wasn't.");
@@ -73,6 +61,7 @@ public class SourceIdAllocatorTest {
@Test
public void allocationTest() throws Exception {
+ mockRace.setRaceStatusEnum(RaceStatusEnum.PRESTART);
int sourceID = sourceIdAllocator.allocateSourceID();
@@ -108,10 +97,7 @@ public class SourceIdAllocatorTest {
@Test
public void reallocationTest() throws Exception {
- List sourceIDList = new ArrayList<>();
- sourceIDList.add(123);
-
- SourceIdAllocator sourceIdAllocator = new SourceIdAllocator(sourceIDList);
+ mockRace.setRaceStatusEnum(RaceStatusEnum.PRESTART);
//Allocate.
int sourceID = sourceIdAllocator.allocateSourceID();
diff --git a/racevisionGame/src/test/java/mock/model/commandFactory/WindCommandTest.java b/racevisionGame/src/test/java/mock/model/commandFactory/WindCommandTest.java
index 2193eb7a..158c43d6 100644
--- a/racevisionGame/src/test/java/mock/model/commandFactory/WindCommandTest.java
+++ b/racevisionGame/src/test/java/mock/model/commandFactory/WindCommandTest.java
@@ -1,11 +1,11 @@
package mock.model.commandFactory;
import mock.exceptions.CommandConstructionException;
-import mock.model.MockBoat;
-import mock.model.MockRace;
-import mock.model.MockRaceTest;
+import mock.exceptions.SourceIDAllocationException;
+import mock.model.*;
import network.Messages.BoatAction;
import network.Messages.Enums.BoatActionEnum;
+import network.Messages.Enums.RaceStatusEnum;
import org.junit.Before;
import org.junit.Test;
import shared.exceptions.InvalidBoatDataException;
@@ -21,6 +21,7 @@ import static org.mockito.Mockito.mock;
*/
public class WindCommandTest {
private MockRace race;
+ private SourceIdAllocator allocator;
private MockBoat boat;
private Command upwind;
private Command downwind;
@@ -29,12 +30,15 @@ public class WindCommandTest {
private double offset = 3.0;
@Before
- public void setUp() throws CommandConstructionException, InvalidBoatDataException, InvalidRegattaDataException, InvalidRaceDataException {
+ public void setUp() throws CommandConstructionException, InvalidBoatDataException, InvalidRegattaDataException, InvalidRaceDataException, SourceIDAllocationException {
race = MockRaceTest.createMockRace();
-
+ allocator = new SourceIdAllocator(race);
+ race.setRaceStatusEnum(RaceStatusEnum.PRESTART);
+ allocator.allocateSourceID();
boat = race.getBoats().get(0);
+
//when(race.getWindDirection()).thenReturn(Bearing.fromDegrees(0.0));
boat.setBearing(Bearing.fromDegrees(45.0));
diff --git a/racevisionGame/src/test/java/visualiser/network/ConnectionToServerParticipantTest.java b/racevisionGame/src/test/java/visualiser/network/ConnectionToServerParticipantTest.java
index 64fdfcb5..4b6ed5e3 100644
--- a/racevisionGame/src/test/java/visualiser/network/ConnectionToServerParticipantTest.java
+++ b/racevisionGame/src/test/java/visualiser/network/ConnectionToServerParticipantTest.java
@@ -94,7 +94,7 @@ public class ConnectionToServerParticipantTest {
incomingCommands.put(command);
//Need to wait for connection thread to execute commands.
- Thread.sleep(250);
+ Thread.sleep(500);
assertEquals(ConnectionToServerState.CONNECTED, connectionToServer.getConnectionState());
assertTrue(connectionToServer.getJoinAcceptance() != null);
diff --git a/racevisionGame/src/test/resources/mock/mockXML/raceTest.xml b/racevisionGame/src/test/resources/mock/mockXML/raceTest.xml
index 4ad5f88f..b10a0158 100644
--- a/racevisionGame/src/test/resources/mock/mockXML/raceTest.xml
+++ b/racevisionGame/src/test/resources/mock/mockXML/raceTest.xml
@@ -5,12 +5,6 @@
2017-04-19T15:30:00+1200
-
-
-
-
-
-
@@ -54,4 +48,4 @@
-
\ No newline at end of file
+
diff --git a/settings/keyBindings.xml b/settings/keyBindings.xml
new file mode 100644
index 00000000..2b807e17
--- /dev/null
+++ b/settings/keyBindings.xml
@@ -0,0 +1,33 @@
+
+
+
+
+ SPACE
+
+
+
+ SHIFT
+
+
+
+ DOWN
+
+
+
+ X
+
+
+
+ ENTER
+
+
+
+ Z
+
+
+
+ UP
+
+
+
+