@ -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 = 20 ms , scale = 5 , frame represents 20 * 5 = 100 ms , and so boats are simulated for 100 ms , even though only 20 ms actually occurred .
* E . g . , frame period = 20 ms , scale = 5 , frame represents 20 * 5 = 100 ms , and so boats are simulated for 100 ms , even though only 20 ms actually occurred .
* /
* /
private int scaleFactor = 2 5;
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 {
/ * *
/ * *
* C hecks if the boat ' s current bearing would put it out of course bounds , and adjusts it if it does .
* C alculates 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 Bearing [ ] calculateBearingBounds ( Boat boat ) {
Bearing [ ] bearings = new Bearing [ 2 ] ;
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 void forceBoatBearingInBounds ( Boat boat ) {
private boolean checkBearingInsideCourse ( Bearing bearing , GPSCoordinate position ) {
//Get the boat's azimuth.
//Get azimuth from bearing .
Azimuth azimuth = Azimuth . fromBearing ( boat . getBearing ( ) ) ;
Azimuth azimuth = Azimuth . fromBearing ( b earing) ;
//Tests to see if a point in front of the boat is out of bounds, if so mirror heading in the wind.
//Tests to see if a point in front of the boat is out of bounds.
double epsilonMeters = 100 d ;
double epsilonMeters = 100 d ;
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 ;
}
}
}
}