@ -48,6 +48,11 @@ public class Race implements Runnable {
* /
private List < GPSCoordinate > boundary ;
/ * *
* A copy of the boundary list , except "shrunk" inwards by 50 m .
* /
private List < GPSCoordinate > shrinkBoundary ;
/ * *
* The elapsed time , in milliseconds , of the race .
* /
@ -63,7 +68,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 = 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 = 5;
private int scaleFactor = 1 5;
/ * *
* The race ID of the course .
@ -121,13 +126,16 @@ public class Race implements Runnable {
this . boats = FXCollections . observableArrayList ( raceData . getBoats ( ) ) ;
this . compoundMarks = FXCollections . observableArrayList ( raceData . getCompoundMarks ( ) ) ;
this . boundary = raceData . getBoundary ( ) ;
this . shrinkBoundary = GPSCoordinate . getShrinkBoundary ( this . boundary ) ;
this . legs = raceData . getLegs ( ) ;
this . legs . add ( new Leg ( "Finish" , this . legs . size ( ) ) ) ;
this . raceId = raceData . getRaceId ( ) ;
//The start time is current time + 4 minutes, scaled. prestart is 3 minutes, and we add another.
this . startTime = System . currentTimeMillis ( ) + ( ( Constants . RacePreStartTime + ( 1 * 1000) ) / this . scaleFactor ) ;
this . startTime = System . currentTimeMillis ( ) + ( ( Constants . RacePreStartTime + ( 1 * 60 * 1000) ) / this . scaleFactor ) ;
this . setRaceStatusEnum ( RaceStatusEnum . NOT_ACTIVE ) ;
this . raceType = RaceTypeEnum . FLEET_RACE ;
@ -374,7 +382,6 @@ public class Race implements Runnable {
} else {
//Otherwise, the race is over!
System . out . println ( "test" ) ;
raceFinished . start ( ) ;
setRaceStatusEnum ( RaceStatusEnum . FINISHED ) ;
this . stop ( ) ;
@ -516,67 +523,64 @@ 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 ( ) ) ;
//Find the VMG inside these bounds.
VMG bestVMG = boat . getPolars ( ) . calculateVMG ( this . windDirection , this . windSpeed , boat . calculateBearingToNextMarker ( ) , lowerAcceptableBound , upperAcceptableBound ) ;
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 bestVMG ;
}
/ * *
* Determines whether or not a given VMG improves the velocity of a boat .
* @param boat The boat to test .
* @param vmg The new VMG to test .
* 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 ( Boat boat , VMG vmg ) {
private boolean improvesVelocity ( VMG currentVMG , VMG potentialVMG , Bearing bearingToDestination ) {
//Calculates the angle between the boat and its destination.
Angle angleBetweenDestAndHeading = Angle . fromDegrees ( boat . getBearing ( ) . degrees ( ) - b oat. calculateBearingToNextMarker ( ) . degrees ( ) ) ;
Angle angleBetweenDestAndHeading = Angle . fromDegrees ( currentVMG . getBearing ( ) . degrees ( ) - b earingToDestination . degrees ( ) ) ;
//Calculates the angle between the new VMG and the boat's destination.
Angle angleBetweenDestAndNewVMG = Angle . fromDegrees ( vmg . getBearing ( ) . degrees ( ) - b oat. calculateBearingToNextMarker ( ) . degrees ( ) ) ;
Angle angleBetweenDestAndNewVMG = Angle . fromDegrees ( potentialVMG . getBearing ( ) . degrees ( ) - b earingToDestination . degrees ( ) ) ;
//Calculate the boat's current velocity.
double currentVelocity = Math . cos ( angleBetweenDestAndHeading . radians ( ) ) * boat. getCurren tSpeed( ) ;
double currentVelocity = Math . cos ( angleBetweenDestAndHeading . radians ( ) ) * currentVMG. ge tSpeed( ) ;
//Calculate the potential velocity with the new VMG.
double vmgVelocity = Math . cos ( angleBetweenDestAndNewVMG . radians ( ) ) * vmg . getSpeed ( ) ;
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 .
* @param vmg The new VMG to test .
* @return True if the new VMG is improves velocity , false otherwise .
* /
private boolean improvesVelocity ( Boat boat , VMG vmg ) {
//Get the boats "current" VMG.
VMG boatVMG = new VMG ( boat . getCurrentSpeed ( ) , boat . getBearing ( ) ) ;
//Check if the new VMG is better than the boat's current VMG.
return this . improvesVelocity ( boatVMG , vmg , boat . calculateBearingToNextMarker ( ) ) ;
}
/ * *
* Calculates the distance a boat has travelled and updates its current position according to this value .
@ -604,31 +608,34 @@ 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 ) ;
//Only get a new VMG if the boat will go outside the course, or X seconds have elapsed.
boolean willStayInsideCourse = this . checkBearingInsideCourse ( boat . getBearing ( ) , boat . getCurrentPosition ( ) ) ;
long tackPeriod = 15000 ;
if ( ! willStayInsideCourse | | ( boat . getTimeSinceTackChange ( ) > tackPeriod ) ) {
//Calculate the new VMG.
VMG newVMG = this . calculateVMG ( boat , bearingBounds ) ;
//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 ) ;
Azimuth azimuth = Azimuth . fromBearing ( boat . getBearing ( ) ) ;
GPSCoordinate testBounds = GPSCoordinate . calculateNewPosition ( boat . getCurrentPosition ( ) ,
( 100.0 / Constants . NMToMetersConversion ) , azimuth ) ;
//If the new vmg improves velocity, use it.
if ( improvesVelocity ( boat , newVMG ) ) {
boat . setVMG ( newVMG ) ;
} else if ( ! GPSCoordinate . isInsideBoundary ( testBounds , boundary ) ) {
//checks to see if the new vmg sends the boat out of bounds and if so mirrors its direction in the wind
double currDegrees = newVMG . getBearing ( ) . degrees ( ) ;
double windDirectionDegrees = this . windDirection . degrees ( ) ;
double tempHeading = ( currDegrees - windDirectionDegrees + 90 ) % 360 ;
newVMG . getBearing ( ) . setDegrees ( tempHeading ) ;
boat . setVMG ( newVMG ) ;
}
//If the new vmg improves velocity, use it.
if ( improvesVelocity ( boat , newVMG ) ) {
boat . setVMG ( newVMG ) ;
} else {
//We also need to use the new VMG if our current bearing will take us out of the course.
if ( ! willStayInsideCourse ) {
boat . setVMG ( newVMG ) ;
}
}
}
this . updateEstimatedTime ( boat ) ;
//Check the boats position (update leg and stuff).
this . checkPosition ( boat , totalTimeElapsed ) ;
@ -645,12 +652,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 ;
double lastAngle = - 1 ;
boolean lastAngleWasGood = false ;
//Check all bearings between [0, 360).
for ( double angle = 0 ; angle < 360 ; angle + = 1 ) {
@ -661,29 +669,29 @@ 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 ;
}
if ( lastAngle ! = - 1 ) {
//The upper bearing will be the last acceptable bearing before finding any unacceptable bearing.
if ( ! foundAnyBadBearing ) {
upperBearing = bearing ;
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 ) ;
}
} else {
foundAnyBadBearing = 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 ;
@ -696,6 +704,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 ) {
@ -704,11 +713,11 @@ public class Race implements Runnable {
//Tests to see if a point in front of the boat is out of bounds.
double epsilonMeters = 10 0d ;
double epsilonMeters = 5 0d ;
GPSCoordinate testCoord = GPSCoordinate . calculateNewPosition ( position , epsilonMeters , azimuth ) ;
//If it isn't inside the boundary, calculate new bearing.
if ( GPSCoordinate . isInsideBoundary ( testCoord , this . b oundary) ) {
if ( GPSCoordinate . isInsideBoundary ( testCoord , this . shrinkB oundary) ) {
return true ;
} else {
return false ;
@ -885,6 +894,7 @@ public class Race implements Runnable {
if ( windDir < 0 ) {
windDir + = 65535 ;
}
this . windDirection = new Bearing ( BoatLocation . convertHeadingIntToDouble ( windDir ) ) ;
}
@ -916,4 +926,4 @@ public class Race implements Runnable {
boat . setEstimatedTime ( startTime + totalTimeElapsed + timeFromNow ) ;
}
}
}
}