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:
fjc40
2017-05-20 00:18:42 +12:00
parent 81ed07330b
commit de0abf94e0
3 changed files with 124 additions and 27 deletions
+95 -19
View File
@@ -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.
* 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.
@@ -483,24 +483,43 @@ public class Race implements Runnable {
/**
* 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) {
private VMG calculateVMG(Boat boat, Bearing[] bearingBounds) {
//How fast a boat can turn, in degrees per millisecond.
double turnRate = 0.03;
//How much the boat is allowed to turn, considering how long since it last turned.
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).
double bound1Degrees = Math.max(boat.getBearing().degrees() - turnAngle, 0);
double bound2Degrees = Math.min(boat.getBearing().degrees() + turnAngle, 360);
double bound1Degrees = boat.getBearing().degrees() - turnAngle;
double bound2Degrees = boat.getBearing().degrees() + turnAngle;
Bearing bound1 = Bearing.fromDegrees(bound1Degrees);
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);
}
@@ -557,9 +576,11 @@ public class Race implements Runnable {
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.
VMG newVMG = this.calculateVMG(boat);
VMG newVMG = this.calculateVMG(boat, bearingBounds);
//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).
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.
* @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.
Azimuth azimuth = Azimuth.fromBearing(boat.getBearing());
Bearing[] bearings = new Bearing[2];
//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;
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 (!GPSCoordinate.isInsideBoundary(testCoord, this.boundary)) {
Bearing tempBearing = Bearing.fromDegrees(boat.getBearing().degrees() - this.windDirection.degrees() + 90);
boat.setBearing(tempBearing);
if (GPSCoordinate.isInsideBoundary(testCoord, this.boundary)) {
return true;
} else {
return false;
}
}