Mock.Polars:
Moved some code into isBearingInsideInterval and isFlippedInterval utility functions. Mock.Race: Appear to have fixed the boats going out bounds issue at last, by fixing a few bugs, simplifying the code, and removing the boat turning speeds.
This commit is contained in:
@@ -118,10 +118,7 @@ public class Polars {
|
||||
|
||||
|
||||
//If the lower bound is greater than the upper bound, we have a "flipped" interval. That is for, e.g., [70, 55] the lower bound is greater than the upper bound, and so it checks that (VMGAngle >= 70 OR VMGAngle =< 55), instead of (VMGAngle >= 70 AND VMGAngle =< 55).
|
||||
boolean flippedInterval = false;
|
||||
if (bearingLowerBound.degrees() > bearingUpperBound.degrees()) {
|
||||
flippedInterval = true;
|
||||
}
|
||||
boolean flippedInterval = Polars.isFlippedInterval(bearingLowerBound, bearingUpperBound);
|
||||
|
||||
|
||||
|
||||
@@ -179,7 +176,7 @@ public class Polars {
|
||||
Bearing bestVMGAngle = Bearing.fromDegrees(0d);
|
||||
|
||||
//Calculate the VMG for all possible angles at this wind speed.
|
||||
for (double angleDegree = 0; angleDegree < 360; angleDegree += 0.1) {
|
||||
for (double angleDegree = 0; angleDegree < 360; angleDegree += 1) {
|
||||
Bearing angle = Bearing.fromDegrees(angleDegree);
|
||||
|
||||
//This is the true bearing of the boat, if it went at the angle against the wind.
|
||||
@@ -191,21 +188,10 @@ public class Polars {
|
||||
|
||||
//Check that the boat's bearing would actually be acceptable.
|
||||
//We continue (skip to next iteration) if it is outside of the interval.
|
||||
if (flippedInterval) {
|
||||
//Bearing must be inside [lower, upper], where lower > upper. So, bearing must be >= lower, or bearing < upper. We use inverted logic since we are skipping if it is true.
|
||||
if ((trueBoatBearing.degrees() < bearingLowerBound.degrees()) & (trueBoatBearing.degrees() > bearingUpperBound.degrees())) {
|
||||
if (!Polars.isBearingInsideInterval(trueBoatBearing, bearingLowerBound, bearingUpperBound)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
} else {
|
||||
//Bearing must be inside [lower, upper].
|
||||
if ((trueBoatBearing.degrees() < bearingLowerBound.degrees()) || (trueBoatBearing.degrees() > bearingUpperBound.degrees())) {
|
||||
continue;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
//Basic linear interpolation. Find the nearest two angles from the table, and interpolate between them.
|
||||
|
||||
@@ -313,6 +299,58 @@ public class Polars {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Determines whether an interval is "flipped". This means that the lower bound is greater than the upper bound (e.g., [290, 43] degrees).
|
||||
* @param lowerBound The lower bound.
|
||||
* @param upperBound The upper bound.
|
||||
* @return True if the interval is flipped, false otherwise.
|
||||
*/
|
||||
public static boolean isFlippedInterval(Bearing lowerBound, Bearing upperBound) {
|
||||
|
||||
//If the lower bound is greater than the upper bound, we have a "flipped" interval.
|
||||
boolean flippedInterval = false;
|
||||
if (lowerBound.degrees() > upperBound.degrees()) {
|
||||
flippedInterval = true;
|
||||
}
|
||||
|
||||
return flippedInterval;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Determines if a bearing is inside an interval.
|
||||
* @param bearing The bearing to check.
|
||||
* @param lowerBound The lower bound of the interval.
|
||||
* @param upperBound The upper bound of the interval.
|
||||
* @return True if the bearing is inside the interval, false otherwise.
|
||||
*/
|
||||
public static boolean isBearingInsideInterval(Bearing bearing, Bearing lowerBound, Bearing upperBound) {
|
||||
|
||||
//Check if it's a flipped interval.
|
||||
boolean flippedInterval = Polars.isFlippedInterval(lowerBound, upperBound);
|
||||
|
||||
if (flippedInterval) {
|
||||
//Bearing must be inside [lower, upper], where lower > upper. So, bearing must be >= lower, or bearing < upper. We use inverted logic since we are skipping if it is true.
|
||||
if ((bearing.degrees() >= lowerBound.degrees()) || (bearing.degrees() <= upperBound.degrees())) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
} else {
|
||||
//Bearing must be inside [lower, upper].
|
||||
if ((bearing.degrees() >= lowerBound.degrees()) && (bearing.degrees() <= upperBound.degrees())) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Calculate the linear interpolation scalar for a value between two bounds. E.g., lower = 7, upper = 10, value = 8, therefore the scalar (or the proportion between the bounds) is 0.333.
|
||||
* Also assumes that the bounds are periodic - e.g., for angles a lower bound of 350deg and upper bound of 5deg is in interval of 15 degrees.
|
||||
|
||||
@@ -502,41 +502,48 @@ public class Race implements Runnable {
|
||||
*/
|
||||
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 = 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);
|
||||
//Find the VMG inside these bounds.
|
||||
VMG bestVMG = boat.getPolars().calculateVMG(this.windDirection, this.windSpeed, boat.calculateBearingToNextMarker(), lowerAcceptableBound, upperAcceptableBound);
|
||||
|
||||
|
||||
//Calculate VMG using these bounds.
|
||||
return boat.getPolars().calculateVMG(this.windDirection, this.windSpeed, boat.calculateBearingToNextMarker(), bound1, bound2);
|
||||
|
||||
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.
|
||||
@@ -545,21 +552,11 @@ public class Race implements Runnable {
|
||||
*/
|
||||
private boolean improvesVelocity(Boat boat, VMG vmg) {
|
||||
|
||||
//Calculates the angle between the boat and its destination.
|
||||
Angle angleBetweenDestAndHeading = Angle.fromDegrees(boat.getBearing().degrees() - boat.calculateBearingToNextMarker().degrees());
|
||||
//Get the boats "current" VMG.
|
||||
VMG boatVMG = new VMG(boat.getCurrentSpeed(), boat.getBearing());
|
||||
|
||||
//Calculates the angle between the new VMG and the boat's destination.
|
||||
Angle angleBetweenDestAndNewVMG = Angle.fromDegrees(vmg.getBearing().degrees() - boat.calculateBearingToNextMarker().degrees());
|
||||
|
||||
|
||||
//Calculate the boat's current velocity.
|
||||
double currentVelocity = Math.cos(angleBetweenDestAndHeading.radians()) * boat.getCurrentSpeed();
|
||||
|
||||
//Calculate the potential velocity with the new VMG.
|
||||
double vmgVelocity = Math.cos(angleBetweenDestAndNewVMG.radians()) * vmg.getSpeed();
|
||||
|
||||
//Return whether or not the new VMG gives better velocity.
|
||||
return vmgVelocity > currentVelocity;
|
||||
//Check if the new VMG is better than the boat's current VMG.
|
||||
return this.improvesVelocity(boatVMG, vmg, boat.calculateBearingToNextMarker());
|
||||
|
||||
}
|
||||
|
||||
@@ -593,6 +590,7 @@ public class Race implements Runnable {
|
||||
//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);
|
||||
|
||||
@@ -626,14 +624,13 @@ public class Race implements Runnable {
|
||||
|
||||
Bearing[] bearings = new Bearing[2];
|
||||
|
||||
Bearing lowerBearing = Bearing.fromDegrees(0);
|
||||
Bearing lowerBearing = Bearing.fromDegrees(0.001);
|
||||
Bearing upperBearing = Bearing.fromDegrees(359.999);
|
||||
|
||||
|
||||
boolean foundAnyBadBearing = false;
|
||||
boolean foundLowerBearing = false;
|
||||
boolean foundAnyGoodBearing = false;
|
||||
boolean stopFindingUpperBearing = false;
|
||||
|
||||
double lastAngle = -1;
|
||||
boolean lastAngleWasGood = false;
|
||||
|
||||
//Check all bearings between [0, 360).
|
||||
for (double angle = 0; angle < 360; angle += 1) {
|
||||
@@ -644,36 +641,28 @@ public class Race implements Runnable {
|
||||
//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;
|
||||
foundAnyGoodBearing = true;
|
||||
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);
|
||||
}
|
||||
|
||||
//The upper bearing will be the last acceptable bearing before finding any unacceptable bearing.
|
||||
if (!foundAnyBadBearing) {
|
||||
upperBearing = bearing;
|
||||
} else if (foundAnyBadBearing && foundAnyGoodBearing && !stopFindingUpperBearing) {
|
||||
upperBearing = bearing;
|
||||
}
|
||||
|
||||
|
||||
} else {
|
||||
|
||||
foundAnyBadBearing = true;
|
||||
|
||||
if (foundAnyBadBearing && foundAnyGoodBearing) {
|
||||
stopFindingUpperBearing = true;
|
||||
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;
|
||||
@@ -687,6 +676,7 @@ public class Race implements Runnable {
|
||||
* 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) {
|
||||
|
||||
|
||||
Reference in New Issue
Block a user