Story 51 rounding fix Acceptance criteria: - The layout must be sent over the API in a race.xml message, plus boat location messages for the marks as required, before any other boat location messages are sent. - Each mark must be defined using boat "SourceID"s (see definition of race.xml) so their positions can be adjusted later. - The course must meet the requirements given in the AC35 Protocol. - The course limits should be set to clear all marks by at least several boat-lengths. - The entire race area within the limits must be on the water. (Work with rounding) Tested by playing the first map with a variety of rotation angles. See merge request !43main
commit
54fe37fd3a
@ -0,0 +1,118 @@
|
|||||||
|
package shared.model;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Contains data related to mark rounding for a specific leg.
|
||||||
|
*/
|
||||||
|
public class MarkRoundingData {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The leg this relates to.
|
||||||
|
*/
|
||||||
|
private Leg leg;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The mark that should be rounded.
|
||||||
|
*/
|
||||||
|
private Mark markToRound;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The bearing of the leg.
|
||||||
|
*/
|
||||||
|
private Bearing legBearing;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The bearing of the next leg.
|
||||||
|
*/
|
||||||
|
private Bearing nextLegBearing;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The location of the first rounding check point.
|
||||||
|
*/
|
||||||
|
private GPSCoordinate roundCheck1;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The location of the second rounding check point.
|
||||||
|
*/
|
||||||
|
private GPSCoordinate roundCheck2;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A halfway point between mark to round and roundCheck1.
|
||||||
|
*/
|
||||||
|
private GPSCoordinate roundCheck1Halfway;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A halfway point between mark to round and roundCheck2.
|
||||||
|
*/
|
||||||
|
private GPSCoordinate roundCheck2Halfway;
|
||||||
|
|
||||||
|
|
||||||
|
public MarkRoundingData() {
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public Leg getLeg() {
|
||||||
|
return leg;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLeg(Leg leg) {
|
||||||
|
this.leg = leg;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Mark getMarkToRound() {
|
||||||
|
return markToRound;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMarkToRound(Mark markToRound) {
|
||||||
|
this.markToRound = markToRound;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Bearing getLegBearing() {
|
||||||
|
return legBearing;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLegBearing(Bearing legBearing) {
|
||||||
|
this.legBearing = legBearing;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Bearing getNextLegBearing() {
|
||||||
|
return nextLegBearing;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setNextLegBearing(Bearing nextLegBearing) {
|
||||||
|
this.nextLegBearing = nextLegBearing;
|
||||||
|
}
|
||||||
|
|
||||||
|
public GPSCoordinate getRoundCheck1() {
|
||||||
|
return roundCheck1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRoundCheck1(GPSCoordinate roundCheck1) {
|
||||||
|
this.roundCheck1 = roundCheck1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public GPSCoordinate getRoundCheck2() {
|
||||||
|
return roundCheck2;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRoundCheck2(GPSCoordinate roundCheck2) {
|
||||||
|
this.roundCheck2 = roundCheck2;
|
||||||
|
}
|
||||||
|
|
||||||
|
public GPSCoordinate getRoundCheck1Halfway() {
|
||||||
|
return roundCheck1Halfway;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRoundCheck1Halfway(GPSCoordinate roundCheck1Halfway) {
|
||||||
|
this.roundCheck1Halfway = roundCheck1Halfway;
|
||||||
|
}
|
||||||
|
|
||||||
|
public GPSCoordinate getRoundCheck2Halfway() {
|
||||||
|
return roundCheck2Halfway;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRoundCheck2Halfway(GPSCoordinate roundCheck2Halfway) {
|
||||||
|
this.roundCheck2Halfway = roundCheck2Halfway;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,210 @@
|
|||||||
|
package shared.model;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
import static shared.enums.RoundingType.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class contains a sequence of points that describe the mark rounding order for a course.
|
||||||
|
*/
|
||||||
|
public class MarkRoundingSequence {
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Legs in the race.
|
||||||
|
*/
|
||||||
|
private List<Leg> legs;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* For each leg, mark rounding information.
|
||||||
|
*/
|
||||||
|
private Map<Leg, MarkRoundingData> roundingPoints;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
public MarkRoundingSequence(List<Leg> legs) {
|
||||||
|
this.legs = legs;
|
||||||
|
generateRoundingPoints();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the rounding points for a given leg.
|
||||||
|
* @param leg Leg to check.
|
||||||
|
* @return Rounding points for leg.
|
||||||
|
*/
|
||||||
|
public MarkRoundingData getRoundingData(Leg leg) {
|
||||||
|
return roundingPoints.get(leg);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates the rounding points for all legs in the race.
|
||||||
|
*/
|
||||||
|
private void generateRoundingPoints() {
|
||||||
|
this.roundingPoints = new HashMap<>(this.legs.size());
|
||||||
|
|
||||||
|
for (int i = 0; i < this.legs.size(); i++) {
|
||||||
|
Leg currentLeg = this.legs.get(i);
|
||||||
|
|
||||||
|
Optional<Leg> nextLeg = Optional.empty();
|
||||||
|
if (i < legs.size() - 1) {
|
||||||
|
nextLeg = Optional.of(this.legs.get(i + 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
generateRoundingPoint(currentLeg, nextLeg);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates the rounding points for a specific leg.
|
||||||
|
* @param currentLeg The leg to generate rounding points for.
|
||||||
|
* @param nextLeg The following leg, used to help generate rounding points. Final leg of race doesn't have a following leg.
|
||||||
|
*/
|
||||||
|
private void generateRoundingPoint(Leg currentLeg, Optional<Leg> nextLeg) {
|
||||||
|
|
||||||
|
Bearing bearingToAddFirstPoint = calculateBearingToAdd(currentLeg);
|
||||||
|
|
||||||
|
GPSCoordinate startCoord = currentLeg.getStartCompoundMark().getAverageGPSCoordinate();
|
||||||
|
GPSCoordinate endCoord = currentLeg.getEndCompoundMark().getAverageGPSCoordinate();
|
||||||
|
Bearing legBearing = GPSCoordinate.calculateBearing(startCoord, endCoord);
|
||||||
|
Bearing nextBearing = legBearing;
|
||||||
|
|
||||||
|
Mark markToRound = currentLeg.getEndCompoundMark().getMarkForRounding(legBearing);
|
||||||
|
|
||||||
|
GPSCoordinate roundCheck1;
|
||||||
|
if (currentLeg.getEndCompoundMark().getMark2() == null) {
|
||||||
|
//End is a single mark.
|
||||||
|
roundCheck1 = calculateRoundingCheckPoint(
|
||||||
|
currentLeg,
|
||||||
|
markToRound,
|
||||||
|
legBearing,
|
||||||
|
bearingToAddFirstPoint);
|
||||||
|
} else {
|
||||||
|
//End is a gate.
|
||||||
|
if (markToRound == currentLeg.getEndCompoundMark().getMark1()) {
|
||||||
|
roundCheck1 = currentLeg.getEndCompoundMark().getMark2().getPosition();
|
||||||
|
} else {
|
||||||
|
roundCheck1 = currentLeg.getEndCompoundMark().getMark1().getPosition();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//TODO the halfway points currently haven't been done properly.
|
||||||
|
|
||||||
|
GPSCoordinate roundCheck1Halfway = calculateRoundingCheckPoint(
|
||||||
|
currentLeg,
|
||||||
|
markToRound,
|
||||||
|
legBearing,
|
||||||
|
bearingToAddFirstPoint);
|
||||||
|
|
||||||
|
|
||||||
|
GPSCoordinate roundCheck2 = roundCheck1;
|
||||||
|
GPSCoordinate roundCheck2Halfway = roundCheck1Halfway;
|
||||||
|
if (nextLeg.isPresent()) {
|
||||||
|
|
||||||
|
Bearing bearingToAddSecondPoint = bearingToAddFirstPoint;//calculateBearingToAdd(nextLeg.get());
|
||||||
|
|
||||||
|
GPSCoordinate startCoord2 = nextLeg.get().getStartCompoundMark().getAverageGPSCoordinate();
|
||||||
|
GPSCoordinate endCoord2 = nextLeg.get().getEndCompoundMark().getAverageGPSCoordinate();
|
||||||
|
nextBearing = GPSCoordinate.calculateBearing(startCoord2, endCoord2);
|
||||||
|
|
||||||
|
roundCheck2 = calculateRoundingCheckPoint(
|
||||||
|
currentLeg,
|
||||||
|
markToRound,
|
||||||
|
nextBearing,
|
||||||
|
bearingToAddSecondPoint);
|
||||||
|
|
||||||
|
roundCheck2Halfway = calculateRoundingCheckPoint(
|
||||||
|
currentLeg,
|
||||||
|
markToRound,
|
||||||
|
nextBearing,
|
||||||
|
bearingToAddSecondPoint);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
MarkRoundingData roundingData = new MarkRoundingData();
|
||||||
|
roundingData.setLeg(currentLeg);
|
||||||
|
|
||||||
|
roundingData.setLegBearing(legBearing);
|
||||||
|
roundingData.setNextLegBearing(nextBearing);
|
||||||
|
|
||||||
|
roundingData.setMarkToRound(markToRound);
|
||||||
|
|
||||||
|
roundingData.setRoundCheck1(roundCheck1);
|
||||||
|
roundingData.setRoundCheck1Halfway(roundCheck1Halfway);
|
||||||
|
|
||||||
|
roundingData.setRoundCheck2(roundCheck2);
|
||||||
|
roundingData.setRoundCheck2Halfway(roundCheck2Halfway);
|
||||||
|
|
||||||
|
|
||||||
|
this.roundingPoints.put(currentLeg, roundingData);
|
||||||
|
|
||||||
|
|
||||||
|
//Rounding points:
|
||||||
|
|
||||||
|
//each mark/gate has a specific mark to round. Call this ROUNDINGMARK
|
||||||
|
// with a mark, it is the mark
|
||||||
|
// with a gate, it depends if it is a starboard or port gate.
|
||||||
|
// it is the mark that allows the boat to enter between both marks of the gate, whilst obeying the starboard/port requirement.
|
||||||
|
|
||||||
|
//let the bearing between start of leg and end of leg be called LEGBEARING
|
||||||
|
|
||||||
|
//the first rounding point is ROUNDINGDISTANCE units away from the ROUNDINGMARK, on an angle perpendicular to LEGBEARING.
|
||||||
|
// the angle means that the rounding mark is at the center of a gate, for gates.
|
||||||
|
|
||||||
|
//the second rounding point is the same as the first, except LEGBEARING is the bearing between end of current leg, and start of next leg.
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculates the location of the rounding check point, which together with the mark to round, forms a line that the boat must cross to round the mark.
|
||||||
|
* @param leg Leg of race to check.
|
||||||
|
* @param markToRound Mark at end of leg to round.
|
||||||
|
* @param legBearing The bearing of the nearest leg. For the first rounding point this is the leg's bearing, for the second rounding point it is the next leg's bearing.
|
||||||
|
* @param bearingToAdd The bearing to add to the leg bearing to get a perpendicular bearing.
|
||||||
|
* @return The location of the rounding point, which together with the mark to round forms a line the boat must cross.
|
||||||
|
*/
|
||||||
|
private GPSCoordinate calculateRoundingCheckPoint(Leg leg, Mark markToRound, Bearing legBearing, Bearing bearingToAdd) {
|
||||||
|
|
||||||
|
|
||||||
|
double roundingDistanceMeters = leg.getEndCompoundMark().getRoundingDistance();
|
||||||
|
|
||||||
|
|
||||||
|
//We project from rounding mark to get the second point which forms the line the boat must cross.
|
||||||
|
/*
|
||||||
|
c2
|
||||||
|
|
|
||||||
|
|
|
||||||
|
r------c1
|
||||||
|
b
|
||||||
|
*/
|
||||||
|
GPSCoordinate roundCheck = GPSCoordinate.calculateNewPosition(
|
||||||
|
markToRound.getPosition(),
|
||||||
|
roundingDistanceMeters,
|
||||||
|
Azimuth.fromDegrees(legBearing.degrees() + bearingToAdd.degrees()) );
|
||||||
|
|
||||||
|
return roundCheck;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculates the bearing that must be added to a leg's bearing to calculate a perpendicular bearing, used for finding rounding points.
|
||||||
|
* @param leg Leg to check.
|
||||||
|
* @return Bearing to add. Will be either +90 or -90.
|
||||||
|
*/
|
||||||
|
private Bearing calculateBearingToAdd(Leg leg) {
|
||||||
|
|
||||||
|
if (leg.getEndCompoundMark().getRoundingType() == Port ||
|
||||||
|
leg.getEndCompoundMark().getRoundingType() == SP) {
|
||||||
|
return Bearing.fromDegrees(90);
|
||||||
|
} else {
|
||||||
|
return Bearing.fromDegrees(-90);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@ -1,33 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<java version="1.8.0_121" class="java.beans.XMLDecoder">
|
|
||||||
<object class="java.util.HashMap">
|
|
||||||
<void method="put">
|
|
||||||
<string>SPACE</string>
|
|
||||||
<object class="visualiser.gameController.Keys.VMGKey"/>
|
|
||||||
</void>
|
|
||||||
<void method="put">
|
|
||||||
<string>SHIFT</string>
|
|
||||||
<object class="visualiser.gameController.Keys.SailsToggleKey"/>
|
|
||||||
</void>
|
|
||||||
<void method="put">
|
|
||||||
<string>LEFT</string>
|
|
||||||
<object class="visualiser.gameController.Keys.UpWindKey"/>
|
|
||||||
</void>
|
|
||||||
<void method="put">
|
|
||||||
<string>X</string>
|
|
||||||
<object class="visualiser.gameController.Keys.ZoomOutKey"/>
|
|
||||||
</void>
|
|
||||||
<void method="put">
|
|
||||||
<string>ENTER</string>
|
|
||||||
<object class="visualiser.gameController.Keys.TackGybeKey"/>
|
|
||||||
</void>
|
|
||||||
<void method="put">
|
|
||||||
<string>RIGHT</string>
|
|
||||||
<object class="visualiser.gameController.Keys.DownWindKey"/>
|
|
||||||
</void>
|
|
||||||
<void method="put">
|
|
||||||
<string>Z</string>
|
|
||||||
<object class="visualiser.gameController.Keys.ZoomInKey"/>
|
|
||||||
</void>
|
|
||||||
</object>
|
|
||||||
</java>
|
|
||||||
Loading…
Reference in new issue