Merge branch 'master' into storyD-3D

# Conflicts:
#	.gitignore
#	racevisionGame/src/main/java/mock/model/RaceServer.java
#	racevisionGame/src/main/java/mock/model/collider/Collider.java
#	racevisionGame/src/main/java/mock/xml/RaceXMLCreator.java
#	racevisionGame/src/main/java/shared/model/Boat.java
#	racevisionGame/src/main/java/shared/model/Mark.java
#	racevisionGame/src/main/java/visualiser/Controllers/HostController.java
#	racevisionGame/src/main/java/visualiser/Controllers/LobbyController.java
#	racevisionGame/src/main/java/visualiser/Controllers/MainController.java
#	racevisionGame/src/main/java/visualiser/Controllers/RaceViewController.java
#	racevisionGame/src/main/java/visualiser/Controllers/TitleController.java
#	racevisionGame/src/main/java/visualiser/layout/Plane3D.java
#	racevisionGame/src/main/java/visualiser/layout/SeaSurface.java
#	racevisionGame/src/main/java/visualiser/layout/View3D.java
#	racevisionGame/src/main/java/visualiser/utils/PerlinNoiseGenerator.java
#	racevisionGame/src/main/resources/visualiser/scenes/title.fxml
This commit is contained in:
Joseph Gardner
2017-09-14 16:34:28 +12:00
107 changed files with 3366 additions and 2018 deletions
@@ -2,14 +2,17 @@ package mock.app;
import mock.dataInput.PolarParser;
import mock.exceptions.EventConstructionException;
import mock.model.*;
import mock.model.MockRace;
import mock.model.Polars;
import mock.model.RaceLogic;
import mock.model.SourceIdAllocator;
import mock.model.commandFactory.CompositeCommand;
import mock.model.wind.RandomWindGenerator;
import mock.model.wind.ShiftingWindGenerator;
import mock.model.wind.WindGenerator;
import mock.xml.RaceXMLCreator;
import network.Messages.Enums.RaceStatusEnum;
import network.Messages.HostGame;
import network.Messages.LatestMessages;
import org.xml.sax.SAXException;
import shared.dataInput.*;
import shared.enums.XMLFileType;
import shared.exceptions.InvalidBoatDataException;
@@ -18,19 +21,12 @@ import shared.exceptions.InvalidRegattaDataException;
import shared.exceptions.XMLReaderException;
import shared.model.Bearing;
import shared.model.Constants;
import shared.xml.XMLUtilities;
import javax.xml.bind.JAXBException;
import javax.xml.parsers.ParserConfigurationException;
import java.io.IOException;
import java.net.Inet4Address;
import java.nio.charset.StandardCharsets;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
@@ -80,6 +76,7 @@ public class Event {
/**
* Constructs an event, using various XML files.
* @param singlePlayer Whether or not to create a single player event.
* @param mapIndex Specifies which map to use.
* @throws EventConstructionException Thrown if we cannot create an Event for any reason.
*/
public Event(boolean singlePlayer, int mapIndex) throws EventConstructionException {
@@ -111,18 +108,23 @@ public class Event {
boatsXMLFile = "mock/mockXML/boatsSinglePlayer.xml";
}
double windAngle = 300;
//Read XML files.
try {
//this.raceXML = RaceXMLCreator.alterRaceToWind(raceXMLFile, 90);
this.raceXML = XMLReader.readXMLFileToString(raceXMLFile, StandardCharsets.UTF_8);
this.raceXML = Event.setRaceXMLAtCurrentTimeToNow(XMLReader.readXMLFileToString(raceXMLFile, StandardCharsets.UTF_8));
if(mapIndex==4){
this.raceXML = Event.setRaceXMLAtCurrentTimeToNow(XMLReader.readXMLFileToString(raceXMLFile, StandardCharsets.UTF_8), 1000, 5000);
//this.raceXML = Event.setRaceXMLAtCurrentTimeToNow(XMLReader.readXMLFileToString(raceXMLFile, StandardCharsets.UTF_8), 1000, 5000);
this.raceXML = RaceXMLCreator.alterRaceToWind(this.raceXML, XMLFileType.Contents, -1, true);
} else {
this.raceXML = RaceXMLCreator.alterRaceToWind(this.raceXML, XMLFileType.Contents, windAngle, false);
}
this.boatXML = XMLReader.readXMLFileToString(boatsXMLFile, StandardCharsets.UTF_8);
this.regattaXML = XMLReader.readXMLFileToString(regattaXMLFile, StandardCharsets.UTF_8);
} catch (XMLReaderException e) {
} catch (XMLReaderException | InvalidRaceDataException e) {
throw new EventConstructionException("Could not read XML files.", e);
}
@@ -149,17 +151,26 @@ public class Event {
this.latestMessages = new LatestMessages();
WindGenerator windGenerator = new ShiftingWindGenerator(
Bearing.fromDegrees(225),
Bearing.fromDegrees(windAngle),
12
);
MockRace mockRace = new MockRace(
boatDataSource,
raceDataSource,
regattaDataSource,
this.boatPolars,
Constants.RaceTimeScale,
windGenerator );
if(mapIndex==4){
mockRace.setRacePreStartTime(1000);
mockRace.setRacePreparatoryTime(5000);
}
RaceLogic newRace = new RaceLogic(
new MockRace(
boatDataSource,
raceDataSource,
regattaDataSource,
this.boatPolars,
Constants.RaceTimeScale,
windGenerator ),
mockRace,
this.latestMessages,
this.compositeCommand);
@@ -194,7 +205,6 @@ public class Event {
//TODO remove this after demo on 18th august!
/**
* Sets the xml description of the race to show the race was created now, and starts in 4 minutes
* @param raceXML The race.xml contents.
@@ -210,8 +220,6 @@ public class Event {
long millisecondsToAdd = racePreStartTime + racePreparatoryTime;
long secondsToAdd = millisecondsToAdd / 1000;
//Scale the time using our time scalar.
secondsToAdd = secondsToAdd / Constants.RaceTimeScale;
DateTimeFormatter dateFormat = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ssZ");
ZonedDateTime creationTime = ZonedDateTime.now();
@@ -221,4 +229,14 @@ public class Event {
return raceXML;
}
/**
* Creates the needed data type for a network packet
* @return hostGame Ac35DataType
* @throws IOException Inet4Address issue
*/
public HostGame getHostedGameData() throws IOException{
String ip = Inet4Address.getLocalHost().getHostAddress();
return new HostGame(ip, 3779,(byte) 1,(byte) 1, RaceStatusEnum.PRESTART, (byte) 1, (byte) 1);
}
}
@@ -60,7 +60,6 @@ public class MockOutput implements RunnableWithFramePeriod {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
//If we get interrupted, exit the function.
Logger.getGlobal().log(Level.WARNING, "MockOutput.run().sleep(waitForXMLs) was interrupted on thread: " + Thread.currentThread(), e);
@@ -72,7 +71,6 @@ public class MockOutput implements RunnableWithFramePeriod {
}
}
long previousFrameTime = System.currentTimeMillis();
@@ -34,7 +34,10 @@ public class MockBoat extends Boat {
*/
private boolean autoVMG = false;
/**
* Indicates whether boat velocity is determined by wind
*/
private boolean velocityDefault = true;
/**
* Constructs a boat object with a given sourceID, name, country/team abbreviation, and polars table.
@@ -300,4 +303,12 @@ public class MockBoat extends Boat {
public void setAutoVMG(boolean autoVMG) {
this.autoVMG = autoVMG;
}
public boolean isVelocityDefault() {
return velocityDefault;
}
public void setVelocityDefault(boolean velocityDefault) {
this.velocityDefault = velocityDefault;
}
}
@@ -1,18 +1,15 @@
package mock.model;
import mock.model.commandFactory.ActiveObserverCommand;
import mock.model.commandFactory.ObserverCommand;
import mock.model.wind.WindGenerator;
import javafx.animation.AnimationTimer;
import mock.model.collider.ColliderRegistry;
import mock.xml.*;
import network.Messages.BoatLocation;
import network.Messages.BoatStatus;
import network.Messages.Enums.BoatStatusEnum;
import network.Messages.Enums.RaceStatusEnum;
import shared.dataInput.BoatDataSource;
import shared.dataInput.RaceDataSource;
import shared.dataInput.RegattaDataSource;
import shared.exceptions.BoatNotFoundException;
import shared.enums.RoundingType;
import shared.model.*;
import shared.model.Bearing;
@@ -64,7 +61,11 @@ public class MockRace extends RaceState {
*/
private Polars polars;
private ActiveObserverCommand activeObserverCommand;
private long racePreStartTime = Constants.RacePreStartTime;
private long racePreparatoryTime = Constants.RacePreparatoryTime;
/**
* Constructs a race object with a given RaceDataSource, BoatDataSource, and RegattaDataSource and sends events to the given mockOutput.
@@ -81,6 +82,7 @@ public class MockRace extends RaceState {
this.setRaceDataSource(raceDataSource);
this.setRegattaDataSource(regattaDataSource);
this.activeObserverCommand = new ActiveObserverCommand();
this.polars = polars;
this.scaleFactor = timeScale;
@@ -119,6 +121,7 @@ public class MockRace extends RaceState {
//Construct a MockBoat using the Boat and Polars.
MockBoat mockBoat = new MockBoat(boat, polars);
mockBoat.setCurrentLeg(this.getLegs().get(0));
mockBoat.setEstimatedTimeAtNextMark(this.getRaceClock().getCurrentTime());
//Update participant list.
getRaceDataSource().getParticipants().add(sourceID);
@@ -126,6 +129,7 @@ public class MockRace extends RaceState {
this.boats.add(mockBoat);
getRaceDataSource().incrementSequenceNumber();
}
/**
@@ -162,15 +166,15 @@ public class MockRace extends RaceState {
long timeToStart = - this.getRaceClock().getDurationMilli();
if (timeToStart > Constants.RacePreStartTime) {
if (timeToStart > racePreStartTime) {
//Time > 3 minutes is the prestart period.
this.setRaceStatusEnum(RaceStatusEnum.PRESTART);
} else if ((timeToStart <= Constants.RacePreStartTime) && (timeToStart >= Constants.RacePreparatoryTime)) {
} else if ((timeToStart <= racePreStartTime) && (timeToStart >= racePreparatoryTime)) {
//Time between [1, 3] minutes is the warning period.
this.setRaceStatusEnum(RaceStatusEnum.WARNING);
} else if ((timeToStart <= Constants.RacePreparatoryTime) && (timeToStart > 0)) {
} else if ((timeToStart <= racePreparatoryTime) && (timeToStart > 0)) {
//Time between (0, 1] minutes is the preparatory period.
this.setRaceStatusEnum(RaceStatusEnum.PREPARATORY);
@@ -183,6 +187,13 @@ public class MockRace extends RaceState {
}
public void setRacePreStartTime(long racePreStartTime) {
this.racePreStartTime = racePreStartTime;
}
public void setRacePreparatoryTime(long racePreparatoryTime) {
this.racePreparatoryTime = racePreparatoryTime;
}
/**
* Sets the status of all boats in the race to RACING.
@@ -355,70 +366,24 @@ public class MockRace extends RaceState {
//Checks if the current boat has finished the race or not.
boolean finish = this.isLastLeg(boat.getCurrentLeg());
if (!finish && totalElapsedMilliseconds >= updatePeriodMilliseconds && boat.isSailsOut()) {
if (!finish && totalElapsedMilliseconds >= updatePeriodMilliseconds) {
checkPosition(boat, totalElapsedMilliseconds);
if (boat.getCurrentSpeed() == 0) {
newOptimalVMG(boat);
boat.setBearing(boat.calculateBearingToNextMarker());
}
setBoatSpeed(boat);
if(boat.isVelocityDefault()) setBoatSpeed(boat);
//Calculates the distance travelled, in meters, in the current timeslice.
double distanceTravelledMeters = boat.calculateMetersTravelled(updatePeriodMilliseconds);
//Scale it.
distanceTravelledMeters = distanceTravelledMeters * this.scaleFactor;
double distanceTravelledMeters = boat.calculateMetersTravelled(updatePeriodMilliseconds) * this.scaleFactor;
checkPosition(boat, totalElapsedMilliseconds);
//Move the boat forwards that many meters, and advances its time counters by enough milliseconds.
boat.moveForwards(distanceTravelledMeters);
boat.setTimeSinceTackChange(boat.getTimeSinceTackChange() + updatePeriodMilliseconds);
if (boat.getAutoVMG()) {
newOptimalVMG(boat);
boat.setAutoVMG(false);
}
} else {
boat.setCurrentSpeed(0);
}
this.updateEstimatedTime(boat);
}
private void newOptimalVMG(MockBoat boat) {
long tackPeriod = 1000;
if (boat.getTimeSinceTackChange() > tackPeriod) {
//System.out.println("optim called");
//Calculate the new VMG.
// VMG newVMG = boat.getPolars().calculateVMG(
// this.getWindDirection(),
// this.getWindSpeed(),
// boat.calculateBearingToNextMarker(),
// Bearing.fromDegrees(0d),
// Bearing.fromDegrees(359.99999d));
VMG newVMG = NewPolars.setBestVMG(this.getWindDirection(), this.getWindSpeed(), boat.getBearing());
//System.out.println(newVMG);
//If the new vmg improves velocity, use it.
/*if (improvesVelocity(boat, newVMG)) {
}*/
boat.setVMG(newVMG);
}
}
private void setBoatSpeed(MockBoat boat) {
// VMG vmg = boat.getPolars().calculateVMG(
// this.getWindDirection(),
// this.getWindSpeed(),
// boat.getBearing(),
// Bearing.fromDegrees(boat.getBearing().degrees() - 1),
// Bearing.fromDegrees(boat.getBearing().degrees() + 1));
//VMG vmg = boat.getPolars().setBestVMG(this.getWindDirection(), this.getWindSpeed(), boat.getBearing());
VMG vmg = new VMG(NewPolars.calculateSpeed(
this.getWindDirection(),
this.getWindSpeed(),
@@ -514,22 +479,28 @@ public class MockRace extends RaceState {
/**
* Checks to be run on boats rounding marks on the port side
* @param boat the boat that is rounding a mark
* @param roundingChecks the checks to run
* @param legBearing the direction of the leg
* @param roundingData The data for the current leg's rounding.
*/
private void boatRoundingCheckPort(MockBoat boat, List<GPSCoordinate> roundingChecks, Bearing legBearing) {
private void boatRoundingCheckPort(MockBoat boat, MarkRoundingData roundingData) {
//boats must pass all checks in order to round a mark
//boolean for if boat has to/needs to pass through a gate
boolean gateCheck = boat.getCurrentLeg().getEndCompoundMark().getMark2() == null || boat.isBetweenGate(boat.getCurrentLeg().getEndCompoundMark());
Mark roundingMark = boat.getCurrentLeg().getEndCompoundMark().getMarkForRounding(legBearing);
switch (boat.getRoundingStatus()) {
case 0://hasn't started rounding
if (boat.isPortSide(roundingMark) &&
GPSCoordinate.passesLine(roundingMark.getPosition(),
roundingChecks.get(0), boat.getPosition(), legBearing) &&
gateCheck && boat.isBetweenGate(roundingMark, Mark.tempMark(roundingChecks.get(0)))) {
if (boat.isPortSide(roundingData.getMarkToRound()) &&
GPSCoordinate.passesLine(
roundingData.getMarkToRound().getPosition(),
roundingData.getRoundCheck1(),
boat.getPosition(),
roundingData.getLegBearing()) &&
gateCheck &&
boat.isBetweenGate(
roundingData.getMarkToRound(),
Mark.tempMark(roundingData.getRoundCheck1())) ) {
boat.increaseRoundingStatus();
if (boat.getCurrentLeg().getLegNumber() + 2 >= getLegs().size()){
//boat has finished race
@@ -538,11 +509,18 @@ public class MockRace extends RaceState {
}
break;
case 1://has been parallel to the mark;
if (boat.isPortSide(roundingMark) &&
GPSCoordinate.passesLine(roundingMark.getPosition(),
roundingChecks.get(1), boat.getPosition(),
Bearing.fromDegrees(legBearing.degrees() - 90)) &&//negative 90 from bearing because of port rounding
boat.isBetweenGate(roundingMark, Mark.tempMark(roundingChecks.get(1)))) {
if (boat.isPortSide(roundingData.getMarkToRound()) &&
GPSCoordinate.passesLine(
roundingData.getMarkToRound().getPosition(),
roundingData.getRoundCheck2(),
boat.getPosition(),
Bearing.fromDegrees(
GPSCoordinate.calculateBearing(
roundingData.getMarkToRound().getPosition(),
roundingData.getRoundCheck2()).degrees() - 90)) &&//negative 90 from bearing because of port rounding
boat.isBetweenGate(
roundingData.getMarkToRound(),
Mark.tempMark(roundingData.getRoundCheck2()))) {
boat.increaseRoundingStatus();
}
break;
@@ -558,23 +536,27 @@ public class MockRace extends RaceState {
/**
* Checks to be run on boats rounding marks on the starboard side
* @param boat the boat that is rounding a mark
* @param roundingChecks the checks to run
* @param legBearing the direction of the leg
* @param roundingData The data for the current leg's rounding.
*/
private void boatRoundingCheckStarboard(MockBoat boat, List<GPSCoordinate> roundingChecks, Bearing legBearing){
private void boatRoundingCheckStarboard(MockBoat boat, MarkRoundingData roundingData){
//boats must pass all checks in order to round a mark
//boolean for if boat has to/needs to pass through a gate
boolean gateCheck = boat.getCurrentLeg().getEndCompoundMark().getMark2() == null || boat.isBetweenGate(boat.getCurrentLeg().getEndCompoundMark());
Mark roundingMark = boat.getCurrentLeg().getEndCompoundMark().getMarkForRounding(legBearing);
switch (boat.getRoundingStatus()) {
case 0://hasn't started rounding
if (boat.isStarboardSide(roundingMark) &&
GPSCoordinate.passesLine(roundingMark.getPosition(),
roundingChecks.get(0), boat.getPosition(), legBearing) &&
if (boat.isStarboardSide(roundingData.getMarkToRound()) &&
GPSCoordinate.passesLine(
roundingData.getMarkToRound().getPosition(),
roundingData.getRoundCheck1(),
boat.getPosition(),
roundingData.getLegBearing()) &&
gateCheck &&
boat.isBetweenGate(roundingMark, Mark.tempMark(roundingChecks.get(0)))) {
boat.isBetweenGate(
roundingData.getMarkToRound(),
Mark.tempMark(roundingData.getRoundCheck1()))) {
boat.increaseRoundingStatus();
if (boat.getCurrentLeg().getLegNumber() + 2 >= getLegs().size()){
//boat has finished race
@@ -583,10 +565,18 @@ public class MockRace extends RaceState {
}
break;
case 1://has been parallel to the mark
if (boat.isStarboardSide(roundingMark) &&
GPSCoordinate.passesLine(roundingMark.getPosition(),
roundingChecks.get(1), boat.getPosition(), Bearing.fromDegrees(legBearing.degrees() + 90)) && //positive 90 from bearing because of starboard rounding
boat.isBetweenGate(roundingMark, Mark.tempMark(roundingChecks.get(1)))) {
if (boat.isStarboardSide(roundingData.getMarkToRound()) &&
GPSCoordinate.passesLine(
roundingData.getMarkToRound().getPosition(),
roundingData.getRoundCheck2(),
boat.getPosition(),
Bearing.fromDegrees(
GPSCoordinate.calculateBearing(
roundingData.getMarkToRound().getPosition(),
roundingData.getRoundCheck2() ).degrees() + 90)) && //positive 90 from bearing because of starboard rounding
boat.isBetweenGate(
roundingData.getMarkToRound(),
Mark.tempMark(roundingData.getRoundCheck2())) ) {
boat.increaseRoundingStatus();
}
break;
@@ -605,67 +595,29 @@ public class MockRace extends RaceState {
* @param timeElapsed The total time, in milliseconds, that has elapsed since the race started.
*/
protected void checkPosition(MockBoat 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 = boat.getCurrentLeg().getEndCompoundMark().getRoundingDistance(); //250 meters.
if (boat.calculateDistanceToNextMarker() < epsilonNauticalMiles) {
//Boat is within an acceptable distance from the mark.
GPSCoordinate startDirectionLinePoint = boat.getCurrentLeg().getStartCompoundMark().getMark1Position();
GPSCoordinate endDirectionLinePoint = boat.getCurrentLeg().getEndCompoundMark().getMark1Position();
Bearing bearingOfDirectionLine = GPSCoordinate.calculateBearing(startDirectionLinePoint, endDirectionLinePoint);
//use the direction line to create three invisible points that act as crossover lines a boat must cross
//to round a mark
double bearingToAdd;
if (boat.getCurrentLeg().getEndCompoundMark().getRoundingType() == RoundingType.Port ||
boat.getCurrentLeg().getEndCompoundMark().getRoundingType() == RoundingType.SP){
bearingToAdd = 90;
}else{
bearingToAdd = -90;
}
GPSCoordinate roundCheck1 = GPSCoordinate.calculateNewPosition(endDirectionLinePoint,
epsilonNauticalMiles, Azimuth.fromDegrees(bearingOfDirectionLine.degrees() + bearingToAdd));
GPSCoordinate roundCheck2;
try{
Leg nextLeg = getLegs().get(getLegs().indexOf(boat.getCurrentLeg()) + 1);
GPSCoordinate startNextDirectionLinePoint = nextLeg.getStartCompoundMark().getMark1Position();
GPSCoordinate endNextDirectionLinePoint = nextLeg.getEndCompoundMark().getMark1Position();
Bearing bearingOfNextDirectionLine = GPSCoordinate.calculateBearing(startNextDirectionLinePoint, endNextDirectionLinePoint);
roundCheck2 = GPSCoordinate.calculateNewPosition(endDirectionLinePoint,
epsilonNauticalMiles, Azimuth.fromDegrees(bearingOfNextDirectionLine.degrees() + bearingToAdd));
}catch(NullPointerException e){
//this is caused by the last leg not being having a leg after it
roundCheck2 = roundCheck1;
}
List<GPSCoordinate> roundingChecks = new ArrayList<GPSCoordinate>(Arrays.asList(roundCheck1, roundCheck2));
switch (boat.getCurrentLeg().getEndCompoundMark().getRoundingType()) {
case SP://Not yet implemented so these gates will be rounded port side
case Port:
boatRoundingCheckPort(boat, roundingChecks, bearingOfDirectionLine);
break;
case PS://not yet implemented so these gates will be rounded starboard side
case Starboard:
boatRoundingCheckStarboard(boat, roundingChecks, bearingOfDirectionLine);
break;
}
switch (boat.getCurrentLeg().getEndCompoundMark().getRoundingType()) {
case SP://Not yet implemented so these gates will be rounded port side
case Port:
boatRoundingCheckPort(
boat,
getMarkRoundingSequence().getRoundingData(boat.getCurrentLeg()) );
break;
case PS://not yet implemented so these gates will be rounded starboard side
case Starboard:
boatRoundingCheckStarboard(
boat,
getMarkRoundingSequence().getRoundingData(boat.getCurrentLeg()) );
break;
}
//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);
}
//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);
}
@@ -754,4 +706,18 @@ public class MockRace extends RaceState {
}
/**
* Made public, so race logic can control it
*/
public void setChanged() {
super.setChanged();
}
public void addVelocityCommand(ObserverCommand c) {
this.activeObserverCommand.changeVelocityCommand(this, c);
}
public void addAngularCommand(ObserverCommand c) {
this.activeObserverCommand.changeAngularCommand(this, c);
}
}
@@ -145,8 +145,6 @@ public class NewPolars {
* @return the best vmg that the boat can change to
*/
public static VMG setBestVMG(Bearing trueWindAngle, double trueWindSpeed, Bearing boatAngle){
//System.out.println("VMG AUTO CALLED");
//speed
double closestSpeed = getClosest(trueWindSpeed, polars.keySet());
double angle = modulateAngle(boatAngle.degrees() - trueWindAngle.degrees());
@@ -181,15 +179,29 @@ public class NewPolars {
}
/**
* gets the angle bound between 0 and 360 following modular arithmetic
* @param angle angle to modulate
* @return resultant angle after modulation.
*/
public static double modulateAngle(double angle){
return (angle % 360 + 360) % 360;
}
/**
* DO NOT DELETE THIS FUNCTIONS THEY ARE USED FOR TESTING PURPOSES
* @return current polars map
*/
@SuppressWarnings("unused")
private Map<Double, TreeMap<Double, Double>> getPolars(){
//this function is just for testing so therefore it is private
return polars;
}
/**
* DO NOT DELETE THESE FUNCTIONS THEY ARE USED FOR TESTING PURPOSES
*/
@SuppressWarnings("unused")
private void printOutLinearInterpolated(){
for (double tws: polars.keySet()){
System.out.println("==================================================");
@@ -2,6 +2,7 @@ package mock.model;
import javafx.animation.AnimationTimer;
import mock.model.collider.Collision;
import mock.model.commandFactory.CollisionCommand;
import mock.model.commandFactory.Command;
import mock.model.commandFactory.CompositeCommand;
import mock.model.commandFactory.CommandFactory;
@@ -148,9 +149,13 @@ public class RaceLogic implements RunnableWithFramePeriod, Observer {
//Get the current time.
long currentTime = System.currentTimeMillis();
//Execute commands from clients.
// Execute commands from clients.
commands.execute();
// Notify Observers
race.setChanged();
race.notifyObservers();
//Update race time.
race.updateRaceTime(currentTime);
@@ -234,11 +239,9 @@ public class RaceLogic implements RunnableWithFramePeriod, Observer {
@Override
public void update(Observable o, Object arg) {
Collision e = (Collision)arg;
// if(e.getBearing().degrees() == 0) System.out.println("Ahead");
// else if(e.getBearing().degrees() < 90) System.out.println("Starboard");
// else if(e.getBearing().degrees() > 270) System.out.println("Port");
// else System.out.println("Behind");
if(arg instanceof Collision) {
Collision collision = (Collision)arg;
commands.addCommand(new CollisionCommand(race, (MockBoat)collision.getBoat()));
}
}
}
@@ -24,6 +24,7 @@ import java.util.logging.Logger;
public class RaceServer {
private MockRace race;
private LatestMessages latestMessages;
private static RaceServer server;
private List<YachtEvent> collisionEvents = new ArrayList<>();
@@ -49,10 +50,17 @@ public class RaceServer {
public RaceServer(MockRace race, LatestMessages latestMessages) {
server = this;
this.race = race;
this.latestMessages = latestMessages;
}
public static void staticUpdateXML() {
if (server != null) {
server.updateXMLFiles();
}
}
/**
* Parses the race to create a snapshot, and places it in latestMessages.
*/
@@ -84,7 +92,6 @@ public class RaceServer {
}
/**
* Checks if the race/boat/regatta data sources have changed, and if they have, update their xml representations.
*/
@@ -38,7 +38,7 @@ public class SourceIdAllocator {
if (!((mockRace.getRaceStatusEnum() == RaceStatusEnum.PRESTART)
|| (mockRace.getRaceStatusEnum() == RaceStatusEnum.WARNING))) {
throw new SourceIDAllocationException("Could not allocate a source ID. Can only allocate during pre-start period. It is currently: " + mockRace.getRaceStatusEnum());
throw new SourceIDAllocationException("Could not allocate a source ID. Can only allocate during pre-start or warning period. It is currently: " + mockRace.getRaceStatusEnum());
}
List<Integer> allocatedIDs = mockRace.getRaceDataSource().getParticipants();
@@ -25,15 +25,9 @@ public abstract class Collider extends Observable implements Locatable {
Bearing relative = Bearing.fromDegrees(absolute.degrees() - boat.getBearing().degrees());
if(actualDistance <= distance) {
Collision collision = new Collision(relative, distance);
Collision collision = new Collision(boat, relative, distance);
// Notify object of collision
onCollisionEnter(boat, collision);
// Notify observers of collision
notifyObservers(collision);
this.setChanged();
//Send out packet to all boats
onCollisionEnter(collision);
return true;
} else return false;
}
@@ -47,8 +41,7 @@ public abstract class Collider extends Observable implements Locatable {
/**
* Handle a collision event
* @param collider Boat that is colliding
* @param e details of collision
*/
public abstract void onCollisionEnter(Boat collider, Collision e);
public abstract void onCollisionEnter(Collision e);
}
@@ -1,5 +1,6 @@
package mock.model.collider;
import mock.model.MockBoat;
import shared.model.Boat;
import shared.model.GPSCoordinate;
@@ -39,7 +40,7 @@ public class ColliderRegistry extends Collider implements Observer {
}
@Override
public void onCollisionEnter(Boat collider, Collision e) {}
public void onCollisionEnter(Collision e) {}
@Override
public GPSCoordinate getPosition() {
@@ -60,7 +61,7 @@ public class ColliderRegistry extends Collider implements Observer {
public void update(Observable o, Object arg) {
Collision collision = (Collision)arg;
notifyObservers(collision);
this.setChanged();
notifyObservers(collision);
}
}
@@ -1,6 +1,7 @@
package mock.model.collider;
import shared.model.Bearing;
import shared.model.Boat;
/**
* Data structure for holding collision details for ray casting and event handling.
@@ -14,13 +15,19 @@ public class Collision {
* Distance from boat centre to target centre
*/
private double distance;
/**
* Boat involved in the collision
*/
private Boat boat;
/**
* Constructor for Collision structure
* @param boat involved in collision
* @param bearing from boat heading to target
* @param distance from boat centre to target centre
*/
public Collision(Bearing bearing, double distance) {
public Collision(Boat boat, Bearing bearing, double distance) {
this.boat = boat;
this.bearing = bearing;
this.distance = distance;
}
@@ -32,4 +39,8 @@ public class Collision {
public double getDistance() {
return distance;
}
public Boat getBoat() {
return boat;
}
}
@@ -0,0 +1,27 @@
package mock.model.commandFactory;
import java.util.Observable;
/**
* Used to track the current active observer command. This is to ensure two commands that do similar things do not overlap.
*/
public class ActiveObserverCommand {
private ObserverCommand currentVelocityCommand;
private ObserverCommand currentAngularCommand;
public ActiveObserverCommand() {
}
public void changeVelocityCommand(Observable o, ObserverCommand c) {
o.deleteObserver(currentVelocityCommand);
o.addObserver(c);
currentVelocityCommand = c;
}
public void changeAngularCommand(Observable o, ObserverCommand c) {
o.deleteObserver(currentAngularCommand);
o.addObserver(c);
currentAngularCommand = c;
}
}
@@ -0,0 +1,45 @@
package mock.model.commandFactory;
import mock.model.MockBoat;
import mock.model.MockRace;
import shared.model.Azimuth;
import shared.model.GPSCoordinate;
import java.util.Observable;
/**
* Command class for collisions
*/
public class CollisionCommand extends ObserverCommand {
private GPSCoordinate startingPosition;
private Azimuth azimuth;
private double distance;
/**
* Constructor for class
* @param race race context
* @param boat boat controlled by command
*/
public CollisionCommand(MockRace race, MockBoat boat) {
super(race, boat);
race.addObserver(this);
}
@Override
public void execute() {
this.azimuth = Azimuth.fromDegrees(boat.getBearing().degrees() - 180d);
this.startingPosition = boat.getPosition();
this.distance = 30;
boat.setVelocityDefault(false);
}
@Override
public void update(Observable o, Object arg) {
if(GPSCoordinate.calculateDistanceMeters(boat.getPosition(), startingPosition) < distance) {
boat.setPosition(GPSCoordinate.calculateNewPosition(boat.getPosition(), 2, azimuth));
} else {
race.deleteObserver(this);
boat.setVelocityDefault(true);
}
}
}
@@ -3,6 +3,8 @@ package mock.model.commandFactory;
import mock.model.MockBoat;
import mock.model.MockRace;
import java.util.Observer;
/**
* Allows RaceLogic to control MockRace state according to the Command pattern
*/
@@ -0,0 +1,20 @@
package mock.model.commandFactory;
import mock.model.MockBoat;
import mock.model.MockRace;
import java.util.Observer;
/**
* Command that can observe the race
*/
public abstract class ObserverCommand implements Command, Observer {
MockRace race;
MockBoat boat;
public ObserverCommand(MockRace race, MockBoat boat) {
this.race = race;
this.boat = boat;
boat.setAutoVMG(false);
}
}
@@ -2,20 +2,50 @@ package mock.model.commandFactory;
import mock.model.MockBoat;
import mock.model.MockRace;
import mock.model.NewPolars;
import mock.model.VMG;
public class SailsCommand implements Command {
private MockRace race;
private MockBoat boat;
import java.util.Observable;
public class SailsCommand extends ObserverCommand {
private boolean sailsOut;
private double goalVelocity;
public SailsCommand(MockRace race, MockBoat boat, Boolean sailsOut) {
this.race = race;
this.boat = boat;
public SailsCommand(MockRace race, MockBoat boat, boolean sailsOut) {
super(race, boat);
race.addVelocityCommand(this);
this.sailsOut = sailsOut;
}
@Override
public void execute() {
this.boat.setSailsOut(this.sailsOut);
boat.setVelocityDefault(false);
if(sailsOut) {
// Accelerate to VMG speed
double polarSpeed = NewPolars.calculateSpeed(race.getWindDirection(), race.getWindSpeed(), boat.getBearing());
VMG vmg = new VMG(polarSpeed, boat.getBearing());
goalVelocity = vmg.getSpeed();
} else {
// Decelerate to 0
goalVelocity = 0;
}
}
@Override
public void update(Observable o, Object arg) {
double acceleration = 0.5;
if(sailsOut && boat.getCurrentSpeed() < goalVelocity) {
boat.setCurrentSpeed(Math.min(goalVelocity, boat.getCurrentSpeed() + acceleration));
} else if (!sailsOut && boat.getCurrentSpeed() > goalVelocity) {
// Apply deceleration to strictly 0 speed
boat.setCurrentSpeed(Math.max(0, boat.getCurrentSpeed() - acceleration));
} else {
// Release boat from SailsCommand control
if(sailsOut) boat.setVelocityDefault(true);
race.deleteObserver(this);
}
}
}
@@ -4,12 +4,16 @@ import mock.model.MockBoat;
import mock.model.MockRace;
import shared.model.Bearing;
import java.util.Observable;
/**
* Command class for tacking and gybing
*/
public class TackGybeCommand implements Command {
private MockRace race;
private MockBoat boat;
public class TackGybeCommand extends ObserverCommand {
private double goalRotation;
private double totalRotation = 0;
private int direction; // -1 for anticlockwise, 1 for clockwise
private double goalAngle;
/**
* Constructor for class
@@ -17,24 +21,32 @@ public class TackGybeCommand implements Command {
* @param boat mock boat to update
*/
public TackGybeCommand(MockRace race, MockBoat boat) {
this.race = race;
this.boat = boat;
super(race, boat);
race.addAngularCommand(this);
}
@Override
public void execute() {
boat.setAutoVMG(false);
double boatAngle = boat.getBearing().degrees();
double windAngle =race.getWindDirection().degrees();
double windAngle = race.getWindDirection().degrees();
double differenceAngle = calcDistance(boatAngle, windAngle);
double angleA = windAngle + differenceAngle;
double angleB = windAngle - differenceAngle;
if(angleA % 360 == boatAngle){
boat.setBearing(Bearing.fromDegrees(angleB));
if (angleA % 360 == boatAngle) {
goalAngle = angleB % 360;
} else {
boat.setBearing(Bearing.fromDegrees(angleA));
goalAngle = angleA % 360;
}
goalRotation = goalAngle - boatAngle;
if (goalRotation < 0) {
goalRotation += 360;
}
if (goalRotation > 180) {
goalRotation = 360 - goalRotation;
direction = -1;
} else {
direction = 1;
}
}
@@ -49,5 +61,16 @@ public class TackGybeCommand implements Command {
return phi > 180 ? 360 - phi : phi;
}
@Override
public void update(Observable o, Object arg) {
double offset = 3.0;
if (totalRotation < goalRotation) {
boat.setBearing(Bearing.fromDegrees(boat.getBearing().degrees() + offset * direction));
totalRotation += offset;
} else {
boat.setBearing(Bearing.fromDegrees(goalAngle));
race.deleteObserver(this);
}
}
}
@@ -2,13 +2,20 @@ package mock.model.commandFactory;
import mock.model.MockBoat;
import mock.model.MockRace;
import mock.model.NewPolars;
import mock.model.VMG;
import shared.model.Bearing;
import java.util.Observable;
/**
* Command class for autoVMG
*/
public class VMGCommand implements Command {
private MockRace race;
private MockBoat boat;
public class VMGCommand extends ObserverCommand {
private double goalAngle;
private double goalRotation;
private double totalRotation = 0;
private int direction;
/**
* Constructor for class
@@ -16,8 +23,8 @@ public class VMGCommand implements Command {
* @param boat mock boat to update
*/
public VMGCommand(MockRace race, MockBoat boat) {
this.race = race;
this.boat = boat;
super(race, boat);
race.addAngularCommand(this);
}
@Override
@@ -27,5 +34,37 @@ public class VMGCommand implements Command {
} else {
boat.setAutoVMG(true);
}
newOptimalVMG(boat);
goalRotation = goalAngle - boat.getBearing().degrees();
if (goalRotation < 0) {
goalRotation += 360;
}
if (goalRotation > 180) {
goalRotation = 360 - goalRotation;
direction = -1;
} else {
direction = 1;
}
}
private void newOptimalVMG(MockBoat boat) {
long tackPeriod = 1000;
if (boat.getTimeSinceTackChange() > tackPeriod) {
VMG newVMG = NewPolars.setBestVMG(race.getWindDirection(), race.getWindSpeed(), boat.getBearing());
goalAngle = newVMG.getBearing().degrees();
}
}
@Override
public void update(Observable o, Object arg) {
double offset = 3.0;
if (totalRotation < goalRotation) {
boat.setBearing(Bearing.fromDegrees(boat.getBearing().degrees() + offset * direction));
totalRotation += offset;
} else {
boat.setBearing(Bearing.fromDegrees(goalAngle));
race.deleteObserver(this);
}
}
}
@@ -4,17 +4,23 @@ import mock.model.MockBoat;
import mock.model.MockRace;
import shared.model.Bearing;
import java.util.Observable;
/**
* Created by connortaylorbrown on 4/08/17.
* Command class for upwind and downwind controls
*/
public class WindCommand implements Command {
private MockRace race;
private MockBoat boat;
public class WindCommand extends ObserverCommand {
private int direction;
/**
* Constructor for class
* @param race race context
* @param boat boat controlled by command
* @param upwind if true, downwind if false
*/
public WindCommand(MockRace race, MockBoat boat, boolean upwind) {
this.race = race;
this.boat = boat;
super(race, boat);
race.addAngularCommand(this);
this.direction = upwind? -1 : 1;
}
@@ -34,4 +40,9 @@ public class WindCommand implements Command {
boat.setBearing(Bearing.fromDegrees(heading + offset));
}
@Override
public void update(Observable o, Object arg) {
race.deleteObserver(this);
}
}
@@ -105,7 +105,6 @@ public class ShiftingWindGenerator implements WindGenerator {
if (shiftedSoFar >= 180){
shiftAnticlockwise = Math.random() > 0.5;
shiftedSoFar = 0;
// System.out.println("Swapping");
}
timeOfLastShift = System.currentTimeMillis();
@@ -1,34 +1,23 @@
package mock.xml;
import org.w3c.dom.Document;
import org.xml.sax.SAXException;
import shared.dataInput.RaceXMLReader;
import shared.enums.XMLFileType;
import shared.exceptions.InvalidRaceDataException;
import shared.exceptions.XMLReaderException;
import shared.model.*;
import shared.xml.Race.*;
import shared.model.CompoundMark;
import shared.model.Constants;
import shared.model.GPSCoordinate;
import shared.xml.Race.XMLCompoundMark;
import shared.xml.Race.XMLLimit;
import shared.xml.Race.XMLMark;
import shared.xml.Race.XMLRace;
import shared.xml.XMLUtilities;
import javax.xml.XMLConstants;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;
import javax.xml.bind.util.JAXBSource;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.Source;
import javax.xml.transform.dom.DOMSource;
import javax.xml.validation.Schema;
import javax.xml.validation.SchemaFactory;
import javax.xml.validation.Validator;
import java.io.File;
import java.io.IOException;
import java.io.StringWriter;
import java.math.BigInteger;
import java.net.URL;
import java.io.InputStream;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
@@ -64,33 +53,54 @@ public class RaceXMLCreator {
/**
* Rotates the race in a specified direction.
* @param s xml file name
* @param s xml file name or contents.
* @param fileType Whether s is a file name or contents.
* @param degrees degrees to rotate
* @param tutorial Whether we wish to run the tutorial - this changes the race start time.
* @return the new xml file as a string
* @throws XMLReaderException if the xml is not readable
* @throws InvalidRaceDataException if the race is invalid
* @throws JAXBException if the Race class cannot be parsed into a xml.
* @throws IOException if the schema file cannot be found
* @throws SAXException error in schema file
* @throws ParserConfigurationException error in parsing the schema file
*/
public static String alterRaceToWind(String s, double degrees) throws XMLReaderException, InvalidRaceDataException, JAXBException, IOException, SAXException, ParserConfigurationException {
RaceXMLReader reader = new RaceXMLReader(s, XMLFileType.ResourcePath);
public static String alterRaceToWind(String s, XMLFileType fileType, double degrees, boolean tutorial) throws XMLReaderException, InvalidRaceDataException {
XMLRace race = XMLUtilities.xmlToClass(
RaceXMLCreator.class.getClassLoader().getResourceAsStream(s),
RaceXMLCreator.class.getClassLoader().getResource("mock/mockXML/schema/raceSchema.xsd"),
XMLRace.class);
RaceXMLReader reader = new RaceXMLReader(s, fileType);
setRaceXMLAtCurrentTimeToNow(race);
try {
double raceOriginalBearing = getLineAngle(getLeewardGate(reader).getMark1Position(), getWindwardGate(reader).getMark1Position());
XMLRace race = XMLUtilities.xmlToClass(
s,
RaceXMLCreator.class.getClassLoader().getResource("mock/mockXML/schema/raceSchema.xsd"),
XMLRace.class);
double degreesToRotate = degrees - raceOriginalBearing;
if(tutorial){
setRaceXMLAtCurrentTimeToNow(race, 1000l, 5000l);
} else {
setRaceXMLAtCurrentTimeToNow(race);
}
alterRaceRotation(race, degreesToRotate);
return XMLUtilities.classToXML(race);
CompoundMark leewardGate = getLeewardGate(reader);
CompoundMark windwardGate = getWindwardGate(reader);
double raceOriginalBearing = 0;
/*if (leewardGate != null && windwardGate != null) {
raceOriginalBearing = getLineAngle(
leewardGate.getMark1Position(),
windwardGate.getMark1Position() );
}*/
double degreesToRotate = degrees - raceOriginalBearing;
if (degrees >= 0) {
alterRaceRotation(race, degreesToRotate);
}
return XMLUtilities.classToXML(race);
} catch (ParserConfigurationException | IOException | SAXException | JAXBException e) {
throw new InvalidRaceDataException("Could not parse or marshall race data file.", e);
}
}
/**
@@ -180,17 +190,12 @@ public class RaceXMLCreator {
}
/**
* Sets the xml description of the race to show the race was created now, and starts in 4 minutes
* @param raceXML The race.xml contents.
*/
public static void setRaceXMLAtCurrentTimeToNow(XMLRace raceXML) {
public static void setRaceXMLAtCurrentTimeToNow(XMLRace raceXML, long racePrestartTime, long racePreparatoryTime){
//The start time is current time + 4 minutes. prestart is 3 minutes, and we add another minute.
long millisecondsToAdd = Constants.RacePreStartTime + 1 * 60 * 1000;
long millisecondsToAdd = racePrestartTime + racePreparatoryTime;
long secondsToAdd = millisecondsToAdd / 1000;
//Scale the time using our time scalar.
secondsToAdd = secondsToAdd / Constants.RaceTimeScale;
DateTimeFormatter dateFormat = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ssZ");
ZonedDateTime creationTime = ZonedDateTime.now();
@@ -198,4 +203,13 @@ public class RaceXMLCreator {
raceXML.getRaceStartTime().setTime(dateFormat.format(creationTime.plusSeconds(secondsToAdd)));
}
/**
* Sets the xml description of the race to show the race was created now, and starts in 4 minutes
* @param raceXML The race.xml contents.
*/
public static void setRaceXMLAtCurrentTimeToNow(XMLRace raceXML) {
setRaceXMLAtCurrentTimeToNow(raceXML, Constants.RacePreStartTime, Constants.RacePreparatoryTime);
}
}