Mock.Race:
Fixed an issue where boats sometimes sailed towards the wind when attempting to turn from bearing just under 360 degrees to a bearing just over 360 degrees. This required an update to the boundary detection code. Visualiser: Appear to have fixed issue where clock didn't display correctly. Previous commit only fixed certain circumstances. #story[873]
This commit is contained in:
@@ -64,7 +64,7 @@ public class Race implements Runnable {
|
|||||||
* Frame periods are multiplied by this to get the amount of time a single frame represents.
|
* 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.
|
* 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 = 25;
|
private int scaleFactor = 5;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The race ID of the course.
|
* The race ID of the course.
|
||||||
@@ -483,24 +483,43 @@ public class Race implements Runnable {
|
|||||||
/**
|
/**
|
||||||
* Calculates a boat's VMG.
|
* Calculates a boat's VMG.
|
||||||
* @param boat The boat to calculate VMG for.
|
* @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.
|
* @return VMG for the specified boat.
|
||||||
*/
|
*/
|
||||||
private VMG calculateVMG(Boat boat) {
|
private VMG calculateVMG(Boat boat, Bearing[] bearingBounds) {
|
||||||
|
|
||||||
//How fast a boat can turn, in degrees per millisecond.
|
//How fast a boat can turn, in degrees per millisecond.
|
||||||
double turnRate = 0.03;
|
double turnRate = 0.03;
|
||||||
|
|
||||||
//How much the boat is allowed to turn, considering how long since it last turned.
|
//How much the boat is allowed to turn, considering how long since it last turned.
|
||||||
double turnAngle = turnRate * boat.getTimeSinceTackChange();
|
double turnAngle = turnRate * boat.getTimeSinceTackChange();
|
||||||
|
//This ensures that the boat bounds don't flip around.
|
||||||
|
turnAngle = Math.min(turnAngle, 179.999);
|
||||||
|
|
||||||
//Find the bounds on what angle the boat is allowed to travel at. The bounds cap out at [0, 360).
|
//Find the bounds on what angle the boat is allowed to travel at. The bounds cap out at [0, 360).
|
||||||
double bound1Degrees = Math.max(boat.getBearing().degrees() - turnAngle, 0);
|
double bound1Degrees = boat.getBearing().degrees() - turnAngle;
|
||||||
double bound2Degrees = Math.min(boat.getBearing().degrees() + turnAngle, 360);
|
double bound2Degrees = boat.getBearing().degrees() + turnAngle;
|
||||||
|
|
||||||
Bearing bound1 = Bearing.fromDegrees(bound1Degrees);
|
Bearing bound1 = Bearing.fromDegrees(bound1Degrees);
|
||||||
Bearing bound2 = Bearing.fromDegrees(bound2Degrees);
|
Bearing bound2 = Bearing.fromDegrees(bound2Degrees);
|
||||||
|
|
||||||
|
|
||||||
|
//Get the lower and upper acceptable bounds.
|
||||||
|
Bearing lowerAcceptableBound = bearingBounds[0];
|
||||||
|
Bearing upperAcceptableBound = bearingBounds[1];
|
||||||
|
|
||||||
|
|
||||||
|
//Find the bounds on what angle the boat can travel on, taking into account both turning rate and the acceptable bounds.
|
||||||
|
bound1Degrees = Math.max(bound1.degrees(), lowerAcceptableBound.degrees());
|
||||||
|
bound2Degrees = Math.min(bound2.degrees(), upperAcceptableBound.degrees());
|
||||||
|
|
||||||
|
bound1 = Bearing.fromDegrees(bound1Degrees);
|
||||||
|
bound2 = Bearing.fromDegrees(bound2Degrees);
|
||||||
|
|
||||||
|
|
||||||
|
//Calculate VMG using these bounds.
|
||||||
return boat.getPolars().calculateVMG(this.windDirection, this.windSpeed, boat.calculateBearingToNextMarker(), bound1, bound2);
|
return boat.getPolars().calculateVMG(this.windDirection, this.windSpeed, boat.calculateBearingToNextMarker(), bound1, bound2);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -557,9 +576,11 @@ public class Race implements Runnable {
|
|||||||
boat.moveForwards(distanceTravelledMeters, updatePeriodMilliseconds * this.scaleFactor);
|
boat.moveForwards(distanceTravelledMeters, updatePeriodMilliseconds * this.scaleFactor);
|
||||||
|
|
||||||
|
|
||||||
|
//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.
|
//Calculate the new VMG.
|
||||||
VMG newVMG = this.calculateVMG(boat);
|
VMG newVMG = this.calculateVMG(boat, bearingBounds);
|
||||||
|
|
||||||
|
|
||||||
//If the new vmg improves velocity, use it.
|
//If the new vmg improves velocity, use it.
|
||||||
@@ -568,12 +589,8 @@ public class Race implements Runnable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
//Ensure that the boat doesn't leave the course bounds.
|
|
||||||
this.forceBoatBearingInBounds(boat);
|
|
||||||
|
|
||||||
|
|
||||||
//Check the boats position (update leg and stuff).
|
//Check the boats position (update leg and stuff).
|
||||||
checkPosition(boat, totalTimeElapsed);
|
this.checkPosition(boat, totalTimeElapsed);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -581,22 +598,81 @@ public class Race implements Runnable {
|
|||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks if the boat's current bearing would put it out of course bounds, and adjusts it if it does.
|
* 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.
|
* @param boat The boat to check.
|
||||||
|
* @return An array of bearings. The first is the lower bound, the second is the upper bound.
|
||||||
*/
|
*/
|
||||||
private void forceBoatBearingInBounds(Boat boat) {
|
private Bearing[] calculateBearingBounds(Boat boat) {
|
||||||
|
|
||||||
//Get the boat's azimuth.
|
Bearing[] bearings = new Bearing[2];
|
||||||
Azimuth azimuth = Azimuth.fromBearing(boat.getBearing());
|
|
||||||
|
|
||||||
//Tests to see if a point in front of the boat is out of bounds, if so mirror heading in the wind.
|
Bearing lowerBearing = Bearing.fromDegrees(0);
|
||||||
|
Bearing upperBearing = Bearing.fromDegrees(359.999);
|
||||||
|
|
||||||
|
|
||||||
|
boolean foundAnyBadBearing = false;
|
||||||
|
boolean foundLowerBearing = 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 (bearingIsGood) {
|
||||||
|
|
||||||
|
//The lower bearing will be the first acceptable bearing after finding any unacceptable bearing.
|
||||||
|
if (foundAnyBadBearing && !foundLowerBearing) {
|
||||||
|
lowerBearing = bearing;
|
||||||
|
foundLowerBearing = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
//The upper bearing will be the last acceptable bearing before finding any unacceptable bearing.
|
||||||
|
if (!foundAnyBadBearing) {
|
||||||
|
upperBearing = bearing;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
foundAnyBadBearing = true;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
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.
|
||||||
|
*/
|
||||||
|
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 = 100d;
|
double epsilonMeters = 100d;
|
||||||
GPSCoordinate testCoord = GPSCoordinate.calculateNewPosition(boat.getCurrentPosition(), epsilonMeters, azimuth);
|
GPSCoordinate testCoord = GPSCoordinate.calculateNewPosition(position, epsilonMeters, azimuth);
|
||||||
|
|
||||||
//If it isn't inside the boundary, calculate new bearing.
|
//If it isn't inside the boundary, calculate new bearing.
|
||||||
if (!GPSCoordinate.isInsideBoundary(testCoord, this.boundary)) {
|
if (GPSCoordinate.isInsideBoundary(testCoord, this.boundary)) {
|
||||||
Bearing tempBearing = Bearing.fromDegrees(boat.getBearing().degrees() - this.windDirection.degrees() + 90);
|
return true;
|
||||||
boat.setBearing(tempBearing);
|
} else {
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -54,6 +54,9 @@ public class StartController extends Controller implements Observer {
|
|||||||
///Tracks whether the race has been started (that is, has startRaceNoScaling() be called).
|
///Tracks whether the race has been started (that is, has startRaceNoScaling() be called).
|
||||||
private boolean hasRaceStarted = false;
|
private boolean hasRaceStarted = false;
|
||||||
|
|
||||||
|
//Tracks whether or not a clock has been created and setup, which occurs after receiving enough information.
|
||||||
|
private boolean hasCreatedClock = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Begins the race with a scale factor of 1
|
* Begins the race with a scale factor of 1
|
||||||
*/
|
*/
|
||||||
@@ -106,7 +109,14 @@ public class StartController extends Controller implements Observer {
|
|||||||
|
|
||||||
private void setRaceClock() {
|
private void setRaceClock() {
|
||||||
raceClock = new RaceClock(raceData.getZonedDateTime());
|
raceClock = new RaceClock(raceData.getZonedDateTime());
|
||||||
timeZoneTime.textProperty().bind(raceClock.timeStringProperty());
|
|
||||||
|
raceClock.timeStringProperty().addListener((observable, oldValue, newValue) -> {
|
||||||
|
Platform.runLater(() -> {
|
||||||
|
timeZoneTime.setText(newValue);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
//TEMP REMOVE
|
||||||
|
//timeZoneTime.textProperty().bind(raceClock.timeStringProperty());
|
||||||
raceClock.run();
|
raceClock.run();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -116,7 +126,12 @@ public class StartController extends Controller implements Observer {
|
|||||||
long utcTime = visualiserInput.getRaceStatus().getExpectedStartTime();
|
long utcTime = visualiserInput.getRaceStatus().getExpectedStartTime();
|
||||||
raceClock.setStartingTime(raceClock.getLocalTime(utcTime));
|
raceClock.setStartingTime(raceClock.getLocalTime(utcTime));
|
||||||
raceStartLabel.setText(DateTimeFormatter.ofPattern(dateFormat).format(raceClock.getStartingTime()));
|
raceStartLabel.setText(DateTimeFormatter.ofPattern(dateFormat).format(raceClock.getStartingTime()));
|
||||||
timer.textProperty().bind(raceClock.durationProperty());
|
|
||||||
|
raceClock.durationProperty().addListener((observable, oldValue, newValue) -> {
|
||||||
|
Platform.runLater(() -> {
|
||||||
|
timer.setText(newValue);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -138,12 +153,15 @@ public class StartController extends Controller implements Observer {
|
|||||||
}
|
}
|
||||||
if (streamedCourse.hasReadCourse()) {
|
if (streamedCourse.hasReadCourse()) {
|
||||||
Platform.runLater(() -> {
|
Platform.runLater(() -> {
|
||||||
setRaceClock();
|
if (!this.hasCreatedClock) {
|
||||||
if(visualiserInput.getRaceStatus() == null){
|
this.hasCreatedClock = true;
|
||||||
return;//TEMP BUG FIX if the race isn't sending race status messages (our mock currently doesn't), then it would block the javafx thread with the previous while loop.
|
setRaceClock();
|
||||||
}// TODO - replace with observer on VisualiserInput
|
if (visualiserInput.getRaceStatus() == null) {
|
||||||
setStartingTime();
|
return;//TEMP BUG FIX if the race isn't sending race status messages (our mock currently doesn't), then it would block the javafx thread with the previous while loop.
|
||||||
setCurrentTime();
|
}// TODO - replace with observer on VisualiserInput
|
||||||
|
setStartingTime();
|
||||||
|
setCurrentTime();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -59,6 +59,8 @@ public class RaceClock implements Runnable {
|
|||||||
this.time = time;
|
this.time = time;
|
||||||
this.timeString.set(DateTimeFormatter.ofPattern("HH:mm:ss dd/MM/YYYY Z").format(time));
|
this.timeString.set(DateTimeFormatter.ofPattern("HH:mm:ss dd/MM/YYYY Z").format(time));
|
||||||
this.lastTime = System.currentTimeMillis();
|
this.lastTime = System.currentTimeMillis();
|
||||||
|
|
||||||
|
|
||||||
if(startingTime != null) {
|
if(startingTime != null) {
|
||||||
long seconds = Duration.between(startingTime.toLocalDateTime(), time.toLocalDateTime()).getSeconds();
|
long seconds = Duration.between(startingTime.toLocalDateTime(), time.toLocalDateTime()).getSeconds();
|
||||||
if(seconds < 0)
|
if(seconds < 0)
|
||||||
@@ -66,6 +68,7 @@ public class RaceClock implements Runnable {
|
|||||||
else
|
else
|
||||||
duration.set(String.format("Time: %02d:%02d:%02d", seconds/3600, (seconds%3600)/60, seconds%60));
|
duration.set(String.format("Time: %02d:%02d:%02d", seconds/3600, (seconds%3600)/60, seconds%60));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Reference in New Issue
Block a user