Refactored and moved XMLReader, RaceDataSource, BoatXMLReader, BoatDataSource, RegattaXMLReader to shared/dataInput.
This commit is contained in:
@@ -1,14 +0,0 @@
|
||||
package mock.dataInput;
|
||||
|
||||
import seng302.Model.Boat;
|
||||
import seng302.Model.Mark;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Boats Data
|
||||
*/
|
||||
public interface BoatDataSource {
|
||||
Map<Integer, Boat> getBoats();
|
||||
Map<Integer, Mark> getMarkerBoats();
|
||||
}
|
||||
@@ -1,149 +0,0 @@
|
||||
package mock.dataInput;
|
||||
|
||||
import org.w3c.dom.Element;
|
||||
import org.w3c.dom.Node;
|
||||
import org.xml.sax.SAXException;
|
||||
import seng302.Model.Boat;
|
||||
import seng302.Model.GPSCoordinate;
|
||||
import seng302.Model.Mark;
|
||||
import seng302.Model.Polars;
|
||||
|
||||
import javax.xml.parsers.ParserConfigurationException;
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Xml Reader class for Boat XML used for the race
|
||||
*/
|
||||
public class BoatXMLReader extends XMLReader implements BoatDataSource {
|
||||
|
||||
private final Map<Integer, Boat> boatMap = new HashMap<>();
|
||||
private final Map<Integer, Mark> markerMap = new HashMap<>();
|
||||
|
||||
/**
|
||||
* Polars table to assign to each boat.
|
||||
*/
|
||||
Polars boatPolars;
|
||||
|
||||
|
||||
/**
|
||||
* Constructor for Boat XML
|
||||
*
|
||||
* @param filePath Name/path of file to read. Read as a resource.
|
||||
* @param boatPolars polars used by the boats
|
||||
* @throws IOException error
|
||||
* @throws SAXException error
|
||||
* @throws ParserConfigurationException error
|
||||
*/
|
||||
public BoatXMLReader(String filePath, Polars boatPolars) throws IOException, SAXException, ParserConfigurationException {
|
||||
super(filePath);
|
||||
this.boatPolars = boatPolars;
|
||||
read();
|
||||
}
|
||||
|
||||
/**
|
||||
* Read the XML
|
||||
*/
|
||||
public void read() {
|
||||
readSettings();
|
||||
readShapes();
|
||||
readBoats();
|
||||
}
|
||||
|
||||
/**
|
||||
* Read the Boats
|
||||
*/
|
||||
private void readBoats() {
|
||||
Element nBoats = (Element) doc.getElementsByTagName("Boats").item(0);
|
||||
for (int i = 0; i < nBoats.getChildNodes().getLength(); i++) {
|
||||
Node boat = nBoats.getChildNodes().item(i);
|
||||
if (boat.getNodeName().equals("Boat")) {
|
||||
readBoatNode(boat);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Ignored data
|
||||
*/
|
||||
private void readShapes() {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Ignored data
|
||||
*/
|
||||
private void readSettings() {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the node (XMl data node) is a Yacht or not
|
||||
* @param boatNode Node from the XML
|
||||
* @return Whether the node is a yacht node or not
|
||||
*/
|
||||
private boolean isYachtNode(Node boatNode) {
|
||||
return boatNode.getAttributes().getNamedItem("Type").getTextContent().toLowerCase().equals("yacht");
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the information about one boat
|
||||
* Ignored values: ShapeID, StoweName, HullNum, Skipper, Type
|
||||
*/
|
||||
private void readBoatNode(Node boatNode) {
|
||||
int sourceID = Integer.parseInt(boatNode.getAttributes().getNamedItem("SourceID").getTextContent());
|
||||
String name = boatNode.getAttributes().getNamedItem("BoatName").getTextContent();
|
||||
|
||||
if (isYachtNode(boatNode)) readYacht(boatNode, sourceID, name);
|
||||
else readMark(boatNode, sourceID, name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Read a Yacht Node
|
||||
* @param boatNode Node to be read
|
||||
* @param sourceID Source ID of the Yacht
|
||||
* @param name Name of the Boat
|
||||
*/
|
||||
private void readYacht(Node boatNode, int sourceID, String name) {
|
||||
String shortName = boatNode.getAttributes().getNamedItem("ShortName").getTextContent();
|
||||
if (exists(boatNode, "Country")) {
|
||||
String country = boatNode.getAttributes().getNamedItem("Country").getTextContent();
|
||||
boatMap.put(sourceID, new Boat(sourceID, name, country, this.boatPolars));
|
||||
} else {
|
||||
boatMap.put(sourceID, new Boat(sourceID, name, shortName, this.boatPolars));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Read Marker Boats
|
||||
* @param boatNode Node to be read
|
||||
* @param sourceID Source ID of the boat
|
||||
* @param name Name of the Marker Boat
|
||||
*/
|
||||
private void readMark(Node boatNode, int sourceID, String name) {
|
||||
Node nCoord = ((Element)boatNode).getElementsByTagName("GPSposition").item(0);
|
||||
double x = Double.parseDouble(nCoord.getAttributes().getNamedItem("X").getTextContent());
|
||||
double y = Double.parseDouble(nCoord.getAttributes().getNamedItem("Y").getTextContent());
|
||||
Mark mark = new Mark(sourceID, name, new GPSCoordinate(y,x));
|
||||
markerMap.put(sourceID, mark);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the boats that are going to participate in this race
|
||||
* @return Dictionary of boats that are to participate in this race indexed by SourceID
|
||||
*/
|
||||
@Override
|
||||
public Map<Integer, Boat> getBoats() {
|
||||
return boatMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the marker BOats that are participating in this race
|
||||
* @return Dictionary of the Markers BOats that are in this race indexed by their Source ID.
|
||||
*/
|
||||
@Override
|
||||
public Map<Integer, Mark> getMarkerBoats() {
|
||||
return markerMap;
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@ package mock.dataInput;
|
||||
|
||||
import mock.exceptions.InvalidPolarFileException;
|
||||
import mock.model.Polars;
|
||||
import shared.model.Bearing;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.ArrayList;
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
package mock.dataInput;
|
||||
|
||||
import seng302.Model.Boat;
|
||||
import seng302.Model.CompoundMark;
|
||||
import seng302.Model.GPSCoordinate;
|
||||
import seng302.Model.Leg;
|
||||
|
||||
import java.time.ZonedDateTime;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Data Class for a Race
|
||||
*/
|
||||
public interface RaceDataSource {
|
||||
List<Boat> getBoats();
|
||||
|
||||
List<Leg> getLegs();
|
||||
|
||||
List<GPSCoordinate> getBoundary();
|
||||
|
||||
List<CompoundMark> getCompoundMarks();
|
||||
|
||||
int getRaceId();
|
||||
|
||||
String getRaceType();
|
||||
|
||||
ZonedDateTime getZonedDateTime();
|
||||
|
||||
GPSCoordinate getMapTopLeft();
|
||||
|
||||
GPSCoordinate getMapBottomRight();
|
||||
}
|
||||
@@ -6,6 +6,8 @@ import org.w3c.dom.Node;
|
||||
import org.w3c.dom.NodeList;
|
||||
import org.xml.sax.SAXException;
|
||||
import seng302.Exceptions.StreamedCourseXMLException;
|
||||
import shared.dataInput.BoatDataSource;
|
||||
import shared.dataInput.RaceDataSource;
|
||||
|
||||
import javax.xml.parsers.ParserConfigurationException;
|
||||
import java.io.IOException;
|
||||
|
||||
@@ -1,115 +0,0 @@
|
||||
package mock.dataInput;
|
||||
|
||||
import org.w3c.dom.Document;
|
||||
import org.w3c.dom.Element;
|
||||
import org.w3c.dom.Node;
|
||||
import org.xml.sax.InputSource;
|
||||
import org.xml.sax.SAXException;
|
||||
|
||||
import javax.xml.parsers.DocumentBuilder;
|
||||
import javax.xml.parsers.DocumentBuilderFactory;
|
||||
import javax.xml.parsers.ParserConfigurationException;
|
||||
import javax.xml.transform.OutputKeys;
|
||||
import javax.xml.transform.Transformer;
|
||||
import javax.xml.transform.TransformerException;
|
||||
import javax.xml.transform.TransformerFactory;
|
||||
import javax.xml.transform.dom.DOMSource;
|
||||
import javax.xml.transform.stream.StreamResult;
|
||||
import java.io.IOException;
|
||||
import java.io.StringReader;
|
||||
import java.io.StringWriter;
|
||||
|
||||
/**
|
||||
* Base Reader for XML Files
|
||||
*/
|
||||
public abstract class XMLReader {
|
||||
|
||||
protected Document doc;
|
||||
|
||||
/**
|
||||
* Read in XML file
|
||||
* @param filePath filepath for XML file
|
||||
* @throws ParserConfigurationException If a document builder cannot be created.
|
||||
* @throws IOException If any IO errors occur while parsing the XML file.
|
||||
* @throws SAXException If any parse error occurs while parsing.
|
||||
*/
|
||||
public XMLReader(String filePath) throws ParserConfigurationException, IOException, SAXException {
|
||||
|
||||
InputSource fXmlFile;
|
||||
if (filePath.contains("<")) {
|
||||
fXmlFile = new InputSource();
|
||||
fXmlFile.setCharacterStream(new StringReader(filePath));
|
||||
|
||||
} else {
|
||||
fXmlFile = new InputSource(getClass().getClassLoader().getResourceAsStream(filePath));
|
||||
}
|
||||
|
||||
DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
|
||||
DocumentBuilder dBuilder = dbFactory.newDocumentBuilder();
|
||||
doc = dBuilder.parse(fXmlFile);
|
||||
doc.getDocumentElement().normalize();
|
||||
}
|
||||
|
||||
/**
|
||||
* Alternate constructor
|
||||
* @param xmlFile File to be read
|
||||
* @param isWholeFile boolean value whether entire file is being passed
|
||||
*/
|
||||
public XMLReader(String xmlFile, Boolean isWholeFile) {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Return Document data of the read-in XML
|
||||
* @return XML document
|
||||
*/
|
||||
public Document getDocument() {
|
||||
return doc;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get content of a tag in an element
|
||||
* @param n Element to read tags from
|
||||
* @param tagName Name of the tag
|
||||
* @return Content of the tag
|
||||
*/
|
||||
public String getTextValueOfNode(Element n, String tagName) {
|
||||
return n.getElementsByTagName(tagName).item(0).getTextContent();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get attributes for an element
|
||||
* @param n Element to read attributes from
|
||||
* @param attr Attributes of element
|
||||
* @return Attributes of element
|
||||
*/
|
||||
public String getAttribute(Element n, String attr) {
|
||||
return n.getAttribute(attr);
|
||||
}
|
||||
|
||||
protected boolean exists(Node node, String attribute) {
|
||||
return node.getAttributes().getNamedItem(attribute) != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the contents of the XML FILe.
|
||||
* @param document holds all xml information
|
||||
* @return String representation of document
|
||||
* @throws TransformerException when document is malformed, and cannot be turned into a string
|
||||
*/
|
||||
public static String getContents(Document document) throws TransformerException {
|
||||
DOMSource source = new DOMSource(document);
|
||||
|
||||
TransformerFactory transformerFactory = TransformerFactory.newInstance();
|
||||
Transformer transformer = transformerFactory.newTransformer();
|
||||
transformer.setOutputProperty(OutputKeys.INDENT, "yes");
|
||||
transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2");
|
||||
|
||||
StringWriter stringWriter = new StringWriter();
|
||||
StreamResult result = new StreamResult(stringWriter);
|
||||
transformer.transform(source, result);
|
||||
|
||||
return stringWriter.toString();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
package mock.model;
|
||||
|
||||
import javafx.util.Pair;
|
||||
import shared.model.Bearing;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
|
||||
@@ -0,0 +1,927 @@
|
||||
package mock.model;
|
||||
|
||||
import javafx.animation.AnimationTimer;
|
||||
import javafx.collections.FXCollections;
|
||||
import mock.app.MockOutput;
|
||||
import shared.dataInput.RaceDataSource;
|
||||
import network.Messages.Enums.RaceStatusEnum;
|
||||
import network.Messages.Enums.RaceTypeEnum;
|
||||
import shared.model.Bearing;
|
||||
import shared.model.Constants;
|
||||
import shared.model.GPSCoordinate;
|
||||
import shared.model.Leg;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
|
||||
import static java.lang.Math.cos;
|
||||
|
||||
|
||||
/**
|
||||
* Represents a yacht race.
|
||||
* Has a course, boats, boundaries, etc...
|
||||
* Is responsible for simulating the race, and sending messages to a MockOutput instance.
|
||||
*/
|
||||
public class Race implements Runnable {
|
||||
|
||||
/**
|
||||
* An observable list of boats in the race.
|
||||
*/
|
||||
private ObservableList<Boat> boats;
|
||||
|
||||
/**
|
||||
* An observable list of compound marks in the race.
|
||||
*/
|
||||
private ObservableList<CompoundMark> compoundMarks;
|
||||
|
||||
/**
|
||||
* A list of legs in the race.
|
||||
*/
|
||||
private List<Leg> legs;
|
||||
|
||||
/**
|
||||
* A list of coordinates describing the boundary of the course.
|
||||
*/
|
||||
private List<GPSCoordinate> boundary;
|
||||
|
||||
/**
|
||||
* A copy of the boundary list, except "shrunk" inwards by 50m.
|
||||
*/
|
||||
private List<GPSCoordinate> shrinkBoundary;
|
||||
|
||||
/**
|
||||
* The elapsed time, in milliseconds, of the race.
|
||||
*/
|
||||
private long totalTimeElapsed;
|
||||
|
||||
/**
|
||||
* The starting timestamp, in milliseconds, of the race.
|
||||
*/
|
||||
private long startTime;
|
||||
|
||||
/**
|
||||
* The scale factor of the race.
|
||||
* Frame periods are multiplied by this to get the amount of time a single frame represents.
|
||||
* E.g., frame period = 20ms, scale = 5, frame represents 20 * 5 = 100ms, and so boats are simulated for 100ms, even though only 20ms actually occurred.
|
||||
*/
|
||||
private int scaleFactor = 5;
|
||||
|
||||
/**
|
||||
* The race ID of the course.
|
||||
*/
|
||||
private int raceId;
|
||||
|
||||
/**
|
||||
* The current status of the race.
|
||||
*/
|
||||
private RaceStatusEnum raceStatusEnum;
|
||||
|
||||
/**
|
||||
* The type of race this is.
|
||||
*/
|
||||
private RaceTypeEnum raceType;
|
||||
|
||||
/**
|
||||
* The percent chance that a boat fails the race, and enters a DNF state, at each checkpoint.
|
||||
* 0 = 0%, 100 = 100%.
|
||||
*/
|
||||
private int dnfChance = 0;
|
||||
|
||||
|
||||
/**
|
||||
* The mockOutput to send messages to.
|
||||
*/
|
||||
private MockOutput mockOutput;
|
||||
|
||||
|
||||
/**
|
||||
* Wind direction bearing.
|
||||
*/
|
||||
private Bearing windDirection;
|
||||
|
||||
/**
|
||||
* Wind speed (knots).
|
||||
* Convert this to millimeters per second before passing to RaceStatus.
|
||||
*/
|
||||
private double windSpeed;
|
||||
|
||||
private double windDirDegrees;
|
||||
private double windDir;
|
||||
private int changeWind = 4;
|
||||
private static final int windUpperBound = 235;
|
||||
private static final int windLowerBound = 215;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Constructs a race object with a given RaceDataSource and sends events to the given mockOutput.
|
||||
* @param raceData Data source for race related data (boats, legs, etc...).
|
||||
* @param mockOutput The mockOutput to send events to.
|
||||
*/
|
||||
public Race(RaceDataSource raceData, MockOutput mockOutput) {
|
||||
|
||||
this.mockOutput = mockOutput;
|
||||
|
||||
this.boats = FXCollections.observableArrayList(raceData.getBoats());
|
||||
this.compoundMarks = FXCollections.observableArrayList(raceData.getCompoundMarks());
|
||||
this.boundary = raceData.getBoundary();
|
||||
this.shrinkBoundary = GPSCoordinate.getShrinkBoundary(this.boundary);
|
||||
|
||||
|
||||
this.legs = raceData.getLegs();
|
||||
this.legs.add(new Leg("Finish", this.legs.size()));
|
||||
|
||||
this.raceId = raceData.getRaceId();
|
||||
|
||||
//The start time is current time + 4 minutes, scaled. prestart is 3 minutes, and we add another.
|
||||
this.startTime = System.currentTimeMillis() + ((Constants.RacePreStartTime + (1 * 60 * 1000)) / this.scaleFactor);
|
||||
|
||||
this.setRaceStatusEnum(RaceStatusEnum.NOT_ACTIVE);
|
||||
this.raceType = raceData.getRaceType();
|
||||
|
||||
this.windSpeed = 12;
|
||||
this.windDirection = Bearing.fromDegrees(180);
|
||||
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Runnable for the thread.
|
||||
*/
|
||||
public void run() {
|
||||
initialiseBoats();
|
||||
initialiseWindDir();
|
||||
countdownTimer.start();
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the compound marker boats through mock output.
|
||||
*/
|
||||
private void parseMarks() {
|
||||
for (CompoundMark compoundMark : this.compoundMarks) {
|
||||
|
||||
//Get the individual marks from the compound mark.
|
||||
Mark mark1 = compoundMark.getMark1();
|
||||
Mark mark2 = compoundMark.getMark2();
|
||||
|
||||
//If they aren't null, parse them (some compound marks only have one mark).
|
||||
if (mark1 != null) {
|
||||
this.parseIndividualMark(mark1);
|
||||
}
|
||||
|
||||
if (mark2 != null) {
|
||||
this.parseIndividualMark(mark2);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses an individual marker boat, and sends it to mockOutput.
|
||||
* @param mark The marker boat to parse.
|
||||
*/
|
||||
private void parseIndividualMark(Mark mark) {
|
||||
|
||||
this.mockOutput.parseBoatLocation(mark.getSourceID(), mark.getPosition().getLatitude(), mark.getPosition().getLongitude(),0,0, totalTimeElapsed+startTime);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the boats in the race, and send it to mockOutput.
|
||||
*/
|
||||
private void parseBoatLocations() {
|
||||
|
||||
//Parse each boat.
|
||||
for (Boat boat : this.boats) {
|
||||
|
||||
this.parseIndividualBoatLocation(boat);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses an individual boat, and sends it to mockOutput.
|
||||
* @param boat The boat to parse.
|
||||
*/
|
||||
private void parseIndividualBoatLocation(Boat boat) {
|
||||
|
||||
this.mockOutput.parseBoatLocation(
|
||||
boat.getSourceID(),
|
||||
boat.getCurrentPosition().getLatitude(),
|
||||
boat.getCurrentPosition().getLongitude(),
|
||||
boat.getBearing().degrees(),
|
||||
boat.getCurrentSpeed(),
|
||||
startTime + totalTimeElapsed
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Updates the race status enumeration based on the current time, in milliseconds.
|
||||
* @param currentTime The current time, in milliseconds.
|
||||
*/
|
||||
private void updateRaceStatusEnum(long currentTime) {
|
||||
|
||||
//The amount of milliseconds until the race starts.
|
||||
long timeToStart = this.startTime - currentTime;
|
||||
|
||||
//Scale the time to start based on the scale factor.
|
||||
long timeToStartScaled = timeToStart / this.scaleFactor;
|
||||
|
||||
|
||||
if (timeToStartScaled > Constants.RacePreStartTime) {
|
||||
//Time > 3 minutes is the prestart period.
|
||||
this.setRaceStatusEnum(RaceStatusEnum.PRESTART);
|
||||
|
||||
} else if ((timeToStartScaled <= Constants.RacePreStartTime) && (timeToStartScaled >= Constants.RacePreparatoryTime)) {
|
||||
//Time between [1, 3] minutes is the warning period.
|
||||
this.setRaceStatusEnum(RaceStatusEnum.WARNING);
|
||||
|
||||
} else if ((timeToStartScaled <= Constants.RacePreparatoryTime) && (timeToStartScaled > 0)) {
|
||||
//Time between (0, 1] minutes is the preparatory period.
|
||||
this.setRaceStatusEnum(RaceStatusEnum.PREPARATORY);
|
||||
|
||||
} else {
|
||||
//Otherwise, the race has started!
|
||||
this.setRaceStatusEnum(RaceStatusEnum.STARTED);
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the race status, and sends it to mockOutput.
|
||||
*/
|
||||
private void parseRaceStatus() {
|
||||
|
||||
//A race status message contains a list of boat statuses.
|
||||
List<BoatStatus> boatStatuses = new ArrayList<>();
|
||||
|
||||
//Add each boat status to the status list.
|
||||
for (Boat boat : boats) {
|
||||
|
||||
BoatStatus boatStatus = new BoatStatus(boat.getSourceID(), boat.getStatus(), boat.getCurrentLeg().getLegNumber(), boat.getEstimatedTime());
|
||||
|
||||
boatStatuses.add(boatStatus);
|
||||
}
|
||||
|
||||
//TODO REFACTOR for consistency, could send parameters to mockOutput instead of the whole racestatus. This will also fix the sequence number issue.
|
||||
|
||||
//Convert wind direction and speed to ints. //TODO this conversion should be done inside the racestatus class.
|
||||
int windDirectionInt = AC35UnitConverter.encodeHeading(this.windDirection.degrees());
|
||||
int windSpeedInt = (int) (windSpeed * Constants.KnotsToMMPerSecond);
|
||||
|
||||
//Create race status object, and send it.
|
||||
RaceStatus raceStatus = new RaceStatus(System.currentTimeMillis(), this.raceId, this.getRaceStatusEnum().getValue(), this.startTime, windDirectionInt, windSpeedInt, this.getRaceType().getValue(), boatStatuses);
|
||||
|
||||
mockOutput.parseRaceStatus(raceStatus);
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets the status of all boats in the race to RACING.
|
||||
*/
|
||||
private void setBoatsStatusToRacing() {
|
||||
|
||||
for (Boat boat : this.boats) {
|
||||
boat.setStatus(BoatStatusEnum.RACING);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Countdown timer until race starts.
|
||||
*/
|
||||
protected AnimationTimer countdownTimer = new AnimationTimer() {
|
||||
|
||||
|
||||
long currentTime = System.currentTimeMillis();
|
||||
|
||||
@Override
|
||||
public void handle(long arg0) {
|
||||
|
||||
//Update the race status based on the current time.
|
||||
updateRaceStatusEnum(this.currentTime);
|
||||
|
||||
//Parse the boat locations.
|
||||
parseBoatLocations();
|
||||
|
||||
//Parse the marks.
|
||||
parseMarks();
|
||||
|
||||
// Change wind direction
|
||||
changeWindDir();
|
||||
|
||||
//Parse the race status.
|
||||
parseRaceStatus();
|
||||
|
||||
|
||||
if (getRaceStatusEnum() == RaceStatusEnum.STARTED) {
|
||||
System.setProperty("javafx.animation.fullspeed", "true");
|
||||
setBoatsStatusToRacing();
|
||||
raceTimer.start();
|
||||
this.stop();
|
||||
}
|
||||
|
||||
//Update the animations timer's time.
|
||||
currentTime = System.currentTimeMillis();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Timer that runs for the duration of the race, until all boats finish.
|
||||
*/
|
||||
private AnimationTimer raceTimer = new AnimationTimer() {
|
||||
|
||||
/**
|
||||
* Start time of loop, in milliseconds.
|
||||
*/
|
||||
long timeRaceStarted = System.currentTimeMillis();
|
||||
|
||||
/**
|
||||
* The time of the previous frame, in milliseconds.
|
||||
*/
|
||||
long lastFrameTime = timeRaceStarted;
|
||||
|
||||
@Override
|
||||
public void handle(long arg0) {
|
||||
|
||||
//Get the current time.
|
||||
long currentTime = System.currentTimeMillis();
|
||||
|
||||
//Update the total elapsed time.
|
||||
totalTimeElapsed = currentTime - this.timeRaceStarted;
|
||||
|
||||
//As long as there is at least one boat racing, we still simulate the race.
|
||||
if (getNumberOfActiveBoats() != 0) {
|
||||
|
||||
//Get the time period of this frame.
|
||||
long framePeriod = currentTime - lastFrameTime;
|
||||
//We actually simulate 20ms istead of the amount of time that has occurred, as that ensure that we don't end up with large frame periods on slow computers, causing position issues.
|
||||
framePeriod = 20;
|
||||
|
||||
|
||||
//For each boat, we update its position, and generate a BoatLocationMessage.
|
||||
for (Boat boat : boats) {
|
||||
|
||||
//If it is still racing, update its position.
|
||||
if (boat.getStatus() == BoatStatusEnum.RACING) {
|
||||
|
||||
updatePosition(boat, framePeriod, totalTimeElapsed);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
} else {
|
||||
//Otherwise, the race is over!
|
||||
raceFinished.start();
|
||||
setRaceStatusEnum(RaceStatusEnum.FINISHED);
|
||||
this.stop();
|
||||
}
|
||||
|
||||
if (getNumberOfActiveBoats() != 0) {
|
||||
// Change wind direction
|
||||
changeWindDir();
|
||||
|
||||
//Parse the boat locations.
|
||||
parseBoatLocations();
|
||||
|
||||
//Parse the marks.
|
||||
parseMarks();
|
||||
|
||||
//Parse the race status.
|
||||
parseRaceStatus();
|
||||
|
||||
|
||||
//Update the last frame time.
|
||||
this.lastFrameTime = currentTime;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Broadcast that the race has finished.
|
||||
*/
|
||||
protected AnimationTimer raceFinished = new AnimationTimer(){
|
||||
int iters = 0;
|
||||
@Override
|
||||
public void handle(long now) {
|
||||
RaceStatus raceStatus = new RaceStatus(System.currentTimeMillis(), raceId, 4, startTime, 0, 2300, 2, new ArrayList<>());
|
||||
mockOutput.parseRaceStatus(raceStatus);
|
||||
if (iters > 500){
|
||||
mockOutput.stop();
|
||||
stop();
|
||||
}
|
||||
iters++;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Initialise the boats in the race.
|
||||
* This sets their starting positions and current legs.
|
||||
*/
|
||||
public void initialiseBoats() {
|
||||
|
||||
//Gets the starting positions of the boats.
|
||||
List<GPSCoordinate> startingPositions = getSpreadStartingPositions();
|
||||
|
||||
//Get iterators for our boat and position lists.
|
||||
Iterator<Boat> boatIt = this.boats.iterator();
|
||||
Iterator<GPSCoordinate> startPositionIt = startingPositions.iterator();
|
||||
|
||||
//Iterate over the pair of lists.
|
||||
while (boatIt.hasNext() && startPositionIt.hasNext()) {
|
||||
|
||||
//Get the next boat and position.
|
||||
Boat boat = boatIt.next();
|
||||
GPSCoordinate startPosition = startPositionIt.next();
|
||||
|
||||
|
||||
//The boat starts on the first leg of the race.
|
||||
boat.setCurrentLeg(this.legs.get(0));
|
||||
|
||||
//Boats start with 0 knots speed.
|
||||
boat.setCurrentSpeed(0d);
|
||||
|
||||
//Place the boat at its starting position.
|
||||
boat.setCurrentPosition(startPosition);
|
||||
|
||||
//Boats start facing their next marker.
|
||||
boat.setBearing(boat.calculateBearingToNextMarker());
|
||||
|
||||
//Sets the boats status to prestart - it changes to racing when the race starts.
|
||||
boat.setStatus(BoatStatusEnum.PRESTART);
|
||||
|
||||
//We set a large time since tack change so that it calculates a new VMG when the simulation starts.
|
||||
boat.setTimeSinceTackChange(999999);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates a list of starting positions for the different boats, so they do not appear cramped at the start line.
|
||||
*
|
||||
* @return A list of starting positions.
|
||||
*/
|
||||
public List<GPSCoordinate> getSpreadStartingPositions() {
|
||||
|
||||
//The first compound marker of the race - the starting gate.
|
||||
CompoundMark compoundMark = this.legs.get(0).getStartCompoundMark();
|
||||
|
||||
//The position of the two markers from the compound marker.
|
||||
GPSCoordinate mark1Position = compoundMark.getMark1Position();
|
||||
GPSCoordinate mark2Position = compoundMark.getMark2Position();
|
||||
|
||||
|
||||
//Calculates the azimuth between the two points.
|
||||
Azimuth azimuth = GPSCoordinate.calculateAzimuth(mark1Position, mark2Position);
|
||||
|
||||
//Calculates the distance between the two points.
|
||||
double distanceMeters = GPSCoordinate.calculateDistanceMeters(mark1Position, mark2Position);
|
||||
|
||||
//The number of boats in the race.
|
||||
int numberOfBoats = this.boats.size();
|
||||
|
||||
//Calculates the distance between each boat. We divide by numberOfBoats + 1 to ensure that no boat is placed on one of the starting gate's marks.
|
||||
double distanceBetweenBoatsMeters = distanceMeters / (numberOfBoats + 1);
|
||||
|
||||
|
||||
//List to store coordinates in.
|
||||
List<GPSCoordinate> positions = new ArrayList<>();
|
||||
|
||||
//We start spacing boats out from mark 1.
|
||||
GPSCoordinate position = mark1Position;
|
||||
|
||||
//For each boat, displace position, and store it.
|
||||
for (int i = 0; i < numberOfBoats; i++) {
|
||||
|
||||
position = GPSCoordinate.calculateNewPosition(position, distanceBetweenBoatsMeters, azimuth);
|
||||
|
||||
positions.add(position);
|
||||
|
||||
}
|
||||
|
||||
return positions;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Calculates a boat's VMG.
|
||||
* @param boat The boat to calculate VMG for.
|
||||
* @param bearingBounds An array containing the lower and upper acceptable bearing bounds to keep the boat in the course.
|
||||
* @return VMG for the specified boat.
|
||||
*/
|
||||
private VMG calculateVMG(Boat boat, Bearing[] bearingBounds) {
|
||||
|
||||
//Get the lower and upper acceptable bounds.
|
||||
Bearing lowerAcceptableBound = bearingBounds[0];
|
||||
Bearing upperAcceptableBound = bearingBounds[1];
|
||||
|
||||
|
||||
//Find the VMG inside these bounds.
|
||||
VMG bestVMG = boat.getPolars().calculateVMG(this.windDirection, this.windSpeed, boat.calculateBearingToNextMarker(), lowerAcceptableBound, upperAcceptableBound);
|
||||
|
||||
|
||||
|
||||
return bestVMG;
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Determines whether or not a given VMG improves the velocity of a boat, if it were currently using currentVMG.
|
||||
* @param currentVMG The current VMG of the boat.
|
||||
* @param potentialVMG The new VMG to test.
|
||||
* @param bearingToDestination The bearing between the boat and its destination.
|
||||
* @return True if the new VMG is improves velocity, false otherwise.
|
||||
*/
|
||||
private boolean improvesVelocity(VMG currentVMG, VMG potentialVMG, Bearing bearingToDestination) {
|
||||
|
||||
//Calculates the angle between the boat and its destination.
|
||||
Angle angleBetweenDestAndHeading = Angle.fromDegrees(currentVMG.getBearing().degrees() - bearingToDestination.degrees());
|
||||
|
||||
//Calculates the angle between the new VMG and the boat's destination.
|
||||
Angle angleBetweenDestAndNewVMG = Angle.fromDegrees(potentialVMG.getBearing().degrees() - bearingToDestination.degrees());
|
||||
|
||||
|
||||
//Calculate the boat's current velocity.
|
||||
double currentVelocity = Math.cos(angleBetweenDestAndHeading.radians()) * currentVMG.getSpeed();
|
||||
|
||||
//Calculate the potential velocity with the new VMG.
|
||||
double vmgVelocity = Math.cos(angleBetweenDestAndNewVMG.radians()) * potentialVMG.getSpeed();
|
||||
|
||||
//Return whether or not the new VMG gives better velocity.
|
||||
return vmgVelocity > currentVelocity;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether or not a given VMG improves the velocity of a boat.
|
||||
* @param boat The boat to test.
|
||||
* @param vmg The new VMG to test.
|
||||
* @return True if the new VMG is improves velocity, false otherwise.
|
||||
*/
|
||||
private boolean improvesVelocity(Boat boat, VMG vmg) {
|
||||
|
||||
//Get the boats "current" VMG.
|
||||
VMG boatVMG = new VMG(boat.getCurrentSpeed(), boat.getBearing());
|
||||
|
||||
//Check if the new VMG is better than the boat's current VMG.
|
||||
return this.improvesVelocity(boatVMG, vmg, boat.calculateBearingToNextMarker());
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Calculates the distance a boat has travelled and updates its current position according to this value.
|
||||
*
|
||||
* @param boat The boat to be updated.
|
||||
* @param updatePeriodMilliseconds The time, in milliseconds, since the last update.
|
||||
* @param totalElapsedMilliseconds The total number of milliseconds that have elapsed since the start of the race.
|
||||
*/
|
||||
protected void updatePosition(Boat boat, long updatePeriodMilliseconds, long totalElapsedMilliseconds) {
|
||||
|
||||
//Checks if the current boat has finished the race or not.
|
||||
boolean finish = this.isLastLeg(boat.getCurrentLeg());
|
||||
|
||||
if (!finish) {
|
||||
|
||||
|
||||
//Calculates the distance travelled, in meters, in the current timeslice.
|
||||
double distanceTravelledMeters = boat.calculateMetersTravelled(updatePeriodMilliseconds);
|
||||
|
||||
//Scale it.
|
||||
distanceTravelledMeters = distanceTravelledMeters * this.scaleFactor;
|
||||
|
||||
|
||||
//Move the boat forwards that many meters, and advances its time counters by enough milliseconds.
|
||||
boat.moveForwards(distanceTravelledMeters, updatePeriodMilliseconds * this.scaleFactor);
|
||||
|
||||
|
||||
//Only get a new VMG if the boat will go outside the course, or X seconds have elapsed.
|
||||
boolean willStayInsideCourse = this.checkBearingInsideCourse(boat.getBearing(), boat.getCurrentPosition());
|
||||
long tackPeriod = 15000;
|
||||
if (!willStayInsideCourse || (boat.getTimeSinceTackChange() > tackPeriod)) {
|
||||
|
||||
//Calculate the boat's bearing bounds, to ensure that it doesn't go out of the course.
|
||||
Bearing[] bearingBounds = this.calculateBearingBounds(boat);
|
||||
|
||||
|
||||
//Calculate the new VMG.
|
||||
VMG newVMG = this.calculateVMG(boat, bearingBounds);
|
||||
|
||||
|
||||
//If the new vmg improves velocity, use it.
|
||||
if (improvesVelocity(boat, newVMG)) {
|
||||
boat.setVMG(newVMG);
|
||||
|
||||
} else {
|
||||
//We also need to use the new VMG if our current bearing will take us out of the course.
|
||||
if (!willStayInsideCourse) {
|
||||
boat.setVMG(newVMG);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.updateEstimatedTime(boat);
|
||||
|
||||
|
||||
//Check the boats position (update leg and stuff).
|
||||
this.checkPosition(boat, totalTimeElapsed);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the upper and lower bounds that the boat may have in order to not go outside of the course.
|
||||
* @param boat The boat to check.
|
||||
* @return An array of bearings. The first is the lower bound, the second is the upper bound.
|
||||
*/
|
||||
private Bearing[] calculateBearingBounds(Boat boat) {
|
||||
|
||||
Bearing[] bearings = new Bearing[2];
|
||||
|
||||
Bearing lowerBearing = Bearing.fromDegrees(0.001);
|
||||
Bearing upperBearing = Bearing.fromDegrees(359.999);
|
||||
|
||||
|
||||
|
||||
double lastAngle = -1;
|
||||
boolean lastAngleWasGood = false;
|
||||
|
||||
//Check all bearings between [0, 360).
|
||||
for (double angle = 0; angle < 360; angle += 1) {
|
||||
|
||||
//Create bearing from angle.
|
||||
Bearing bearing = Bearing.fromDegrees(angle);
|
||||
|
||||
//Check that if it is acceptable.
|
||||
boolean bearingIsGood = this.checkBearingInsideCourse(bearing, boat.getCurrentPosition());
|
||||
|
||||
|
||||
if (lastAngle != -1) {
|
||||
|
||||
if (lastAngleWasGood && !bearingIsGood) {
|
||||
//We have flipped over from good bearings to bad bearings. So the last good bearing is the upper bearing.
|
||||
upperBearing = Bearing.fromDegrees(lastAngle);
|
||||
}
|
||||
|
||||
if (!lastAngleWasGood && bearingIsGood) {
|
||||
//We have flipped over from bad bearings to good bearings. So the current bearing is the lower bearing.
|
||||
lowerBearing = Bearing.fromDegrees(angle);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
lastAngle = angle;
|
||||
lastAngleWasGood = bearingIsGood;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
//TODO BUG if it can't find either upper or lower, it returns (0, 359.999). Should return (boatbearing, boatbearing+0.0001)
|
||||
bearings[0] = lowerBearing;
|
||||
bearings[1] = upperBearing;
|
||||
|
||||
return bearings;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Checks if a given bearing, starting at a given position, would put a boat out of the course boundaries.
|
||||
* @param bearing The bearing to check.
|
||||
* @param position The position to start from.
|
||||
* @return True if the bearing would keep the boat in the course, false if it would take it out of the course.
|
||||
*/
|
||||
private boolean checkBearingInsideCourse(Bearing bearing, GPSCoordinate position) {
|
||||
|
||||
//Get azimuth from bearing.
|
||||
Azimuth azimuth = Azimuth.fromBearing(bearing);
|
||||
|
||||
|
||||
//Tests to see if a point in front of the boat is out of bounds.
|
||||
double epsilonMeters = 50d;
|
||||
GPSCoordinate testCoord = GPSCoordinate.calculateNewPosition(position, epsilonMeters, azimuth);
|
||||
|
||||
//If it isn't inside the boundary, calculate new bearing.
|
||||
if (GPSCoordinate.isInsideBoundary(testCoord, this.shrinkBoundary)) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Checks if a boat has finished any legs, or has pulled out of race (DNF).
|
||||
* @param boat The boat to check.
|
||||
* @param timeElapsed The total time, in milliseconds, that has elapsed since the race started.
|
||||
*/
|
||||
protected void checkPosition(Boat boat, long timeElapsed) {
|
||||
|
||||
//The distance, in nautical miles, within which the boat needs to get in order to consider that it has reached the marker.
|
||||
double epsilonNauticalMiles = 100.0 / Constants.NMToMetersConversion; //100 meters. TODO should be more like 5-10.
|
||||
|
||||
if (boat.calculateDistanceToNextMarker() < epsilonNauticalMiles) {
|
||||
//Boat has reached its target marker, and has moved on to a new leg.
|
||||
|
||||
|
||||
|
||||
//Calculate how much the boat overshot the marker by.
|
||||
double overshootMeters = boat.calculateDistanceToNextMarker();
|
||||
|
||||
|
||||
//Move boat on to next leg.
|
||||
Leg nextLeg = this.legs.get(boat.getCurrentLeg().getLegNumber() + 1);
|
||||
boat.setCurrentLeg(nextLeg);
|
||||
|
||||
//Add overshoot distance into the distance travelled for the next leg.
|
||||
boat.setDistanceTravelledInLeg(overshootMeters);
|
||||
|
||||
//Setting a high value for this allows the boat to immediately do a large turn, as it needs to in order to get to the next mark.
|
||||
boat.setTimeSinceTackChange(999999);
|
||||
|
||||
|
||||
//Check if the boat has finished or stopped racing.
|
||||
|
||||
if (this.isLastLeg(boat.getCurrentLeg())) {
|
||||
//Boat has finished.
|
||||
boat.setTimeFinished(timeElapsed);
|
||||
boat.setCurrentSpeed(0);
|
||||
boat.setStatus(BoatStatusEnum.FINISHED);
|
||||
} else if (doNotFinish()) {
|
||||
//Boat has pulled out of race.
|
||||
boat.setTimeFinished(timeElapsed);
|
||||
boat.setCurrentLeg(new Leg("DNF", -1));
|
||||
boat.setCurrentSpeed(0);
|
||||
boat.setStatus(BoatStatusEnum.DNF);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Determines whether or not a specific leg is the last leg in the race.
|
||||
* @param leg The leg to check.
|
||||
* @return Returns true if it is the last, false otherwse.
|
||||
*/
|
||||
private boolean isLastLeg(Leg leg) {
|
||||
|
||||
//Get the last leg.
|
||||
Leg lastLeg = this.legs.get(this.legs.size() - 1);
|
||||
|
||||
//Check its ID.
|
||||
int lastLegID = lastLeg.getLegNumber();
|
||||
|
||||
//Get the specified leg's ID.
|
||||
int legID = leg.getLegNumber();
|
||||
|
||||
|
||||
//Check if they are the same.
|
||||
return legID == lastLegID;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets the chance each boat has of failing at a gate or marker
|
||||
*
|
||||
* @param chance percentage chance a boat has of failing per checkpoint.
|
||||
*/
|
||||
protected void setDnfChance(int chance) {
|
||||
if (chance >= 0 && chance <= 100) {
|
||||
dnfChance = chance;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Decides if a boat should received a DNF status.
|
||||
* @return True means it should DNF, false means it shouldn't.
|
||||
*/
|
||||
protected boolean doNotFinish() {
|
||||
Random rand = new Random();
|
||||
return rand.nextInt(100) < dnfChance;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the current race status.
|
||||
* @return The current race status.
|
||||
*/
|
||||
public RaceStatusEnum getRaceStatusEnum() {
|
||||
return raceStatusEnum;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the current race status.
|
||||
* @param raceStatusEnum The new status of the race.
|
||||
*/
|
||||
private void setRaceStatusEnum(RaceStatusEnum raceStatusEnum) {
|
||||
this.raceStatusEnum = raceStatusEnum;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the type of race this is.
|
||||
* @return The type of race this is.
|
||||
*/
|
||||
public RaceTypeEnum getRaceType() {
|
||||
return raceType;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the number of boats that are still active in the race.
|
||||
* They become inactive by either finishing or withdrawing.
|
||||
* @return The number of boats still active in the race.
|
||||
*/
|
||||
protected int getNumberOfActiveBoats() {
|
||||
|
||||
int numberofActiveBoats = 0;
|
||||
|
||||
for (Boat boat : this.boats) {
|
||||
|
||||
//If the boat is currently racing, count it.
|
||||
if (boat.getStatus() == BoatStatusEnum.RACING) {
|
||||
numberofActiveBoats++;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return numberofActiveBoats;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns an observable list of boats in the race.
|
||||
* @return List of boats in the race.
|
||||
*/
|
||||
public ObservableList<Boat> getBoats() {
|
||||
return boats;
|
||||
}
|
||||
|
||||
protected void initialiseWindDir(){
|
||||
windDirDegrees = 225;
|
||||
windDir = AC35UnitConverter.convertHeading(windDirDegrees);
|
||||
/*windDir = new Random().nextInt(65535+1);
|
||||
windDir = BoatLocation.convertHeadingIntToDouble(255);*/
|
||||
this.windDirection = new Bearing((int)windDir);
|
||||
}
|
||||
|
||||
protected void changeWindDir(){
|
||||
int r = new Random().nextInt(changeWind)+1;
|
||||
if(r==1){
|
||||
windDirDegrees = (0.5 + windDirDegrees) % 360;
|
||||
} else if (r==2){
|
||||
windDirDegrees = ((windDirDegrees - 0.5) + 360) % 360;///keep the degrees positive when below 0
|
||||
}
|
||||
if (windDirDegrees > windUpperBound){
|
||||
windDirDegrees = windUpperBound;
|
||||
}
|
||||
if (windDirDegrees < windLowerBound){
|
||||
windDirDegrees = windLowerBound;
|
||||
}
|
||||
|
||||
windDir = AC35UnitConverter.convertHeading(windDirDegrees);
|
||||
this.windDirection = new Bearing(windDirDegrees);
|
||||
}
|
||||
|
||||
protected void setChangeWind(int changeVal){
|
||||
if (changeVal>=0){
|
||||
changeWind = changeVal;
|
||||
}
|
||||
}
|
||||
|
||||
protected int getWind(){
|
||||
return (int)windDir;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the boat's estimated time to next mark if positive
|
||||
* @param boat to estimate time given its velocity
|
||||
*/
|
||||
private void updateEstimatedTime(Boat boat) {
|
||||
double velocityToMark = boat.getCurrentSpeed() * cos(boat.getBearing().radians() - boat.calculateBearingToNextMarker().radians()) / Constants.KnotsToMMPerSecond;
|
||||
if (velocityToMark > 0) {
|
||||
long timeFromNow = (long)(1000*boat.calculateDistanceToNextMarker()/velocityToMark);
|
||||
boat.setEstimatedTime(startTime + totalTimeElapsed + timeFromNow);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user