Merge branch 'develop' into Story71_TackAndGybeSmoothly

This commit is contained in:
Alistair McIntyre
2017-08-10 17:58:19 +12:00
14 changed files with 439 additions and 275 deletions
+184 -30
View File
@@ -10,6 +10,8 @@ import javafx.beans.property.ReadOnlyDoubleWrapper;
import javafx.beans.property.ReadOnlyLongProperty;
import javafx.beans.property.ReadOnlyLongWrapper;
import javafx.scene.paint.Color;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import seng302.gameServer.GameState;
import seng302.model.mark.CompoundMark;
import seng302.model.mark.Mark;
@@ -28,7 +30,12 @@ public class Yacht {
void notifyLocation(Yacht yacht, double lat, double lon, double heading, double velocity);
}
private static final Double ROUNDING_DISTANCE = 15d; // TODO: 3/08/17 wmu16 - Look into this value further
private Logger logger = LoggerFactory.getLogger(Yacht.class);
private static final Integer SPEED_MULTIPLIER = 4;
private static final Double ROUNDING_DISTANCE = 50d; // TODO: 3/08/17 wmu16 - Look into this value further
//BOTH AFAIK
private String boatType;
@@ -39,16 +46,15 @@ public class Yacht {
private String country;
private Long estimateTimeAtFinish;
private Long lastMark;
private Integer currentMarkSeqID = 0;
private Long markRoundTime;
private Double distanceToNextMark;
private Double distanceToCurrentMark;
private Long timeTillNext;
private CompoundMark nextMark;
private Double heading;
private Integer legNumber = 0;
//SERVER SIDE
private final Double TURN_STEP = 3.0;
private final Double TURN_STEP = 5.0;
private Double lastHeading;
private Boolean sailIn;
private GeoPoint location;
@@ -60,8 +66,9 @@ public class Yacht {
//MARK ROUNDING INFO
private GeoPoint lastLocation; //For purposes of mark rounding calculations
private Boolean hasEnteredRoundingZone; //The distance that the boat must be from the mark to round
private Boolean hasPassedFirstLine; //The line extrapolated from the next mark to the current mark
private Boolean hasPassedSecondLine; //The line extrapolated from the last mark to the current mark
private Boolean hasPassedLine;
private Boolean hasPassedThroughGate;
private Boolean finishedRace;
//CLIENT SIDE
private List<YachtLocationListener> locationListeners = new ArrayList<>();
@@ -88,8 +95,9 @@ public class Yacht {
this.velocity = 0d; //in mms-1
this.hasEnteredRoundingZone = false;
this.hasPassedFirstLine = false;
this.hasPassedSecondLine = false;
this.hasPassedLine = false;
this.hasPassedThroughGate = false;
this.finishedRace = false;
}
/**
@@ -101,8 +109,7 @@ public class Yacht {
Double windSpeedKnots = GameState.getWindSpeedKnots();
Double trueWindAngle = Math.abs(GameState.getWindDirection() - heading);
Double boatSpeedInKnots = PolarTable.getBoatSpeed(windSpeedKnots, trueWindAngle);
Double maxBoatSpeed = boatSpeedInKnots / 1.943844492 * 1000;
// TODO: 10/08/17 ajm412: this acceleration stuff should be its own method, and shouldn't have all these magic numbers, could possibly be modelled better.
Double maxBoatSpeed = boatSpeedInKnots / 1.943844492 * 1000 * SPEED_MULTIPLIER;
if (sailIn && velocity <= maxBoatSpeed && maxBoatSpeed != 0d) {
if (velocity < maxBoatSpeed) {
@@ -113,6 +120,7 @@ public class Yacht {
}
} else { // Deceleration
if (velocity > 0d) {
if (maxBoatSpeed != 0d) {
velocity -= maxBoatSpeed / 600;
@@ -130,26 +138,29 @@ public class Yacht {
}
//UPDATE BOAT LOCATION
lastLocation = location;
location = GeoUtility.getGeoCoordinate(location, heading, velocity * secondsElapsed);
//CHECK FOR MARK ROUNDING
distanceToNextMark = calcDistanceToNextMark();
if (distanceToNextMark < ROUNDING_DISTANCE) {
hasEnteredRoundingZone = true;
if (!finishedRace) {
checkForLegProgression();
}
// TODO: 3/08/17 wmu16 - Implement line cross check here
}
/**
* Calculates the distance to the next mark (closest of the two if a gate mark).
*
* Calculates the distance to the next mark (closest of the two if a gate mark). For purposes
* of mark rounding
* @return A distance in metres. Returns -1 if there is no next mark
* @throws IndexOutOfBoundsException If the next mark is null (ie the last mark in the race)
* Check first using {@link seng302.model.mark.MarkOrder#isLastMark(Integer)}
*/
public Double calcDistanceToNextMark() {
if (nextMark == null) {
return -1d;
} else if (nextMark.isGate()) {
public Double calcDistanceToCurrentMark() throws IndexOutOfBoundsException {
CompoundMark nextMark = GameState.getMarkOrder().getCurrentMark(currentMarkSeqID);
if (nextMark.isGate()) {
Mark sub1 = nextMark.getSubMark(1);
Mark sub2 = nextMark.getSubMark(2);
Double distance1 = GeoUtility.getDistance(location, sub1);
@@ -160,6 +171,144 @@ public class Yacht {
}
}
/**
* 4 Different cases of progression in the race
* 1 - Passing the start line
* 2 - Passing any in-race Gate
* 3 - Passing any in-race Mark
* 4 - Passing the finish line
*/
private void checkForLegProgression() {
CompoundMark currentMark = GameState.getMarkOrder().getCurrentMark(currentMarkSeqID);
if (currentMarkSeqID == 0) {
checkStartLineCrossing(currentMark);
} else if (GameState.getMarkOrder().isLastMark(currentMarkSeqID)) {
checkFinishLineCrossing(currentMark);
} else if (currentMark.isGate()) {
checkGateRounding(currentMark);
} else {
checkMarkRounding(currentMark);
}
}
/**
* If we pass the start line gate in the correct direction, progress
*
* @param currentMark The current gate
*/
private void checkStartLineCrossing(CompoundMark currentMark) {
Mark mark1 = currentMark.getSubMark(1);
Mark mark2 = currentMark.getSubMark(2);
CompoundMark nextMark = GameState.getMarkOrder().getNextMark(currentMarkSeqID);
Integer crossedLine = GeoUtility.checkCrossedLine(mark1, mark2, lastLocation, location);
if (crossedLine > 0) {
Boolean isClockwiseCross = GeoUtility.isClockwise(mark1, mark2, nextMark.getMidPoint());
if (crossedLine == 2 && isClockwiseCross || crossedLine == 1 && !isClockwiseCross) {
currentMarkSeqID++;
logMarkRounding(currentMark);
}
}
}
/**
* This algorithm checks for mark rounding. And increments the currentMarSeqID number attribute
* of the yacht if so.
* A visual representation of this algorithm can be seen on the Wiki under
* 'mark passing algorithm'
*/
private void checkMarkRounding(CompoundMark currentMark) {
distanceToCurrentMark = calcDistanceToCurrentMark();
GeoPoint nextPoint = GameState.getMarkOrder().getNextMark(currentMarkSeqID).getMidPoint();
GeoPoint prevPoint = GameState.getMarkOrder().getPreviousMark(currentMarkSeqID)
.getMidPoint();
GeoPoint midPoint = GeoUtility.getDirtyMidPoint(nextPoint, prevPoint);
//1 TEST FOR ENTERING THE ROUNDING DISTANCE
if (distanceToCurrentMark < ROUNDING_DISTANCE) {
hasEnteredRoundingZone = true;
}
//In case current mark is a gate, loop through all marks just in case
for (Mark thisCurrentMark : currentMark.getMarks()) {
if (GeoUtility.isPointInTriangle(lastLocation, location, midPoint, thisCurrentMark)) {
hasPassedLine = true;
}
}
if (hasPassedLine && hasEnteredRoundingZone) {
currentMarkSeqID++;
hasPassedLine = false;
hasEnteredRoundingZone = false;
hasPassedThroughGate = false;
logMarkRounding(currentMark);
}
}
/**
* Checks if a gate line has been crossed and in the correct direction
*
* @param currentMark The current gate
*/
private void checkGateRounding(CompoundMark currentMark) {
Mark mark1 = currentMark.getSubMark(1);
Mark mark2 = currentMark.getSubMark(2);
CompoundMark prevMark = GameState.getMarkOrder().getPreviousMark(currentMarkSeqID);
CompoundMark nextMark = GameState.getMarkOrder().getNextMark(currentMarkSeqID);
Integer crossedLine = GeoUtility.checkCrossedLine(mark1, mark2, lastLocation, location);
//We have crossed the line
if (crossedLine > 0) {
Boolean isClockwiseCross = GeoUtility.isClockwise(mark1, mark2, prevMark.getMidPoint());
//Check we cross the line in the correct direction
if (crossedLine == 1 && isClockwiseCross || crossedLine == 2 && !isClockwiseCross) {
hasPassedThroughGate = true;
}
}
Boolean prevMarkSide = GeoUtility.isClockwise(mark1, mark2, prevMark.getMidPoint());
Boolean nextMarkSide = GeoUtility.isClockwise(mark1, mark2, nextMark.getMidPoint());
if (hasPassedThroughGate) {
//Check if we need to round this gate after passing through
if (prevMarkSide == nextMarkSide) {
checkMarkRounding(currentMark);
} else {
currentMarkSeqID++;
logMarkRounding(currentMark);
}
}
}
/**
* If we pass the finish gate in the correct direction
*
* @param currentMark The current gate
*/
private void checkFinishLineCrossing(CompoundMark currentMark) {
Mark mark1 = currentMark.getSubMark(1);
Mark mark2 = currentMark.getSubMark(2);
CompoundMark prevMark = GameState.getMarkOrder().getPreviousMark(currentMarkSeqID);
Integer crossedLine = GeoUtility.checkCrossedLine(mark1, mark2, lastLocation, location);
if (crossedLine > 0) {
Boolean isClockwiseCross = GeoUtility.isClockwise(mark1, mark2, prevMark.getMidPoint());
if (crossedLine == 1 && isClockwiseCross || crossedLine == 2 && !isClockwiseCross) {
currentMarkSeqID++;
finishedRace = true;
logMarkRounding(currentMark);
System.out.println("YAY YOU FINISHED!");
// TODO: 8/08/17 wmu16 - Do something!
}
}
}
/**
* Adjusts the heading of the boat by a given amount, while recording the boats
* last heading.
@@ -434,14 +583,6 @@ public class Yacht {
this.lastMarkRounded = lastMarkRounded;
}
public void setNextMark(CompoundMark nextMark) {
this.nextMark = nextMark;
}
public CompoundMark getNextMark(){
return nextMark;
}
public GeoPoint getLocation() {
return location;
}
@@ -488,6 +629,7 @@ public class Yacht {
this.timeTillNext = timeTillNext;
}
public Color getColour() {
return colour;
}
@@ -496,6 +638,7 @@ public class Yacht {
this.colour = colour;
}
public Double getVelocity() {
return velocity;
}
@@ -504,8 +647,8 @@ public class Yacht {
this.velocity = velocity;
}
public Double getDistanceToNextMark() {
return distanceToNextMark;
public Double getDistanceToCurrentMark() {
return distanceToCurrentMark;
}
public void updateLocation(double lat, double lng, double heading, double velocity) {
@@ -518,6 +661,17 @@ public class Yacht {
}
}
private void logMarkRounding(CompoundMark currentMark) {
String typeString = "mark";
if (currentMark.isGate()) {
typeString = "gate";
}
System.out.println(
"(" + currentMarkSeqID + ") Passed " + typeString + ": " + currentMark.getMarks().get(0)
.getName()
+ " ID(" + currentMark.getId() + ")");
}
public void addLocationListener (YachtLocationListener listener) {
locationListeners.add(listener);
}
@@ -1,8 +1,9 @@
package seng302.model.mark;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import seng302.model.GeoPoint;
import seng302.utilities.GeoUtility;
public class CompoundMark {
@@ -10,18 +11,17 @@ public class CompoundMark {
private String name;
private List<Mark> marks = new ArrayList<>();
private GeoPoint midPoint;
public CompoundMark(int markID, String name) {
this.compoundMarkId = markID;
public CompoundMark(int markID, String name, List<Mark> marks) {
this.compoundMarkId = markID;
this.name = name;
}
public void addSubMarks(Mark... marks) {
this.marks.addAll(Arrays.asList(marks));
}
public void addSubMarks(List<Mark> marks) {
this.marks.addAll(marks);
this.marks.addAll(marks);
if (marks.size() > 1) {
this.midPoint = GeoUtility.getDirtyMidPoint(marks.get(0), marks.get(1));
} else {
this.midPoint = marks.get(0);
}
}
/**
@@ -68,6 +68,16 @@ public class CompoundMark {
}
}
/**
* NOTE: This is a 'dirty' mid point as it is simply calculated as an xy point would be.
* NO CHECKING FOR LAT / LNG WRAPPING IS DONE IN CREATION OF THIS MIDPOINT
*
* @return GeoPoint of the midpoint of the two marks, or the one mark if there is only one
*/
public GeoPoint getMidPoint() {
return midPoint;
}
/**
* Returns whether or not this CompoundMark is a Gate. It is generally cleaner to program to a
* specific singleMark or the list of marks.
@@ -87,38 +97,6 @@ public class CompoundMark {
return marks;
}
// @Override
// public boolean equals(Object other) {
// if (other == null) {
// return false;
// }
//
// if (!(other instanceof Mark)){
// return false;
// }
//
// Mark otherMark = (Mark) other;
//
// if (otherMark.getLat() != getLat() || otherMark.getLongitude() != getLongitude()) {
// return false;
// }
//
// if (otherMark.getCompoundMarkID() != getCompoundMarkID()){
// return false;
// }
//
// if (otherMark.getId() != getId()){
// return false;
// }
//
// if (!otherMark.getName().equals(name)){
// return false;
// }
//
// return true;
// }
@Override
public int hashCode() {
int hash = 0;
+31 -33
View File
@@ -24,7 +24,7 @@ import java.util.Map;
* Class to hold the order of the marks in the race.
*/
public class MarkOrder {
private List<Mark> raceMarkOrder;
private List<CompoundMark> raceMarkOrder;
private Logger logger = LoggerFactory.getLogger(MarkOrder.class);
public MarkOrder(){
@@ -35,7 +35,7 @@ public class MarkOrder {
* @return An ordered list of marks in the race
* OR null if the mark order could not be loaded
*/
public List<Mark> getMarkOrder(){
public List<CompoundMark> getMarkOrder(){
if (raceMarkOrder == null){
logger.warn("Race order accessed but not instantiated");
return null;
@@ -45,26 +45,35 @@ public class MarkOrder {
}
/**
* Returns the mark in the race after the previous mark
* @param position The current race position
* @return the next race position
* OR null if there is no position
* @param seqID The seqID of the current mark the boat is heading to
* @return A Boolean indicating if this coming mark is the last one (finish line)
*/
public RacePosition getNextPosition(RacePosition position){
Mark previousMark = position.getNextMark();
Mark nextMark;
public Boolean isLastMark(Integer seqID) {
return seqID == raceMarkOrder.size() - 1;
}
if (position.getPositionIndex() + 1 >= raceMarkOrder.size() - 1){
RacePosition nextRacePosition = new RacePosition(raceMarkOrder.size() - 1, null, previousMark);
nextRacePosition.setFinishingLeg();
/**
* @param currentSeqID The seqID of the current mark the boat is heading to
* @return The mark last passed
* @throws IndexOutOfBoundsException if there is no next mark.
* Check seqID != 0 first
*/
public CompoundMark getPreviousMark(Integer currentSeqID) throws IndexOutOfBoundsException{
return raceMarkOrder.get(currentSeqID - 1);
}
return nextRacePosition;
}
public CompoundMark getCurrentMark(Integer currentSeqID) {
return raceMarkOrder.get(currentSeqID);
}
Integer nextPositionIndex = position.getPositionIndex() + 1;
RacePosition nextRacePosition = new RacePosition(nextPositionIndex, raceMarkOrder.get(nextPositionIndex), previousMark);
return nextRacePosition;
/**
* @param currentSeqID The seqID of the current mark the boat is heading to
* @return The mark following the mark that the boat is heading to
* @throws IndexOutOfBoundsException if there is no next mark.
* Check using {@link #isLastMark(Integer)}
*/
public CompoundMark getNextMark(Integer currentSeqID) throws IndexOutOfBoundsException{
return raceMarkOrder.get(currentSeqID + 1);
}
/**
@@ -72,7 +81,7 @@ public class MarkOrder {
* @param xml An AC35 RaceXML
* @return An ordered list of marks in the race
*/
private List<Mark> loadRaceOrderFromXML(String xml){
private List<CompoundMark> loadRaceOrderFromXML(String xml){
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
DocumentBuilder db;
@@ -92,11 +101,11 @@ public class MarkOrder {
logger.debug("Loaded RaceXML for mark order");
List<Corner> corners = data.getMarkSequence();
Map<Integer, CompoundMark> marks = data.getCompoundMarks();
List<Mark> course = new ArrayList<>();
List<CompoundMark> course = new ArrayList<>();
for (Corner corner : corners){
CompoundMark compoundMark = marks.get(corner.getCompoundMarkID());
course.add(compoundMark.getMarks().get(0));
course.add(compoundMark);
}
return course;
@@ -105,17 +114,6 @@ public class MarkOrder {
return null;
}
/**
* @return The first position in the race
*/
public RacePosition getFirstPosition(){
if (raceMarkOrder.size() > 0){
return new RacePosition(-1, raceMarkOrder.get(0), null);
}
return null;
}
/**
* Load the raceXML and mark order
*/
@@ -132,4 +130,4 @@ public class MarkOrder {
}
raceMarkOrder = loadRaceOrderFromXML(raceXML);
}
}
}
@@ -1,55 +0,0 @@
package seng302.model.mark;
/**
* Represents a boats position between two marks
*/
public class RacePosition {
private Integer positionIndex;
private Mark nextMark;
private Mark previousMark;
private Boolean isFinishingLeg;
public RacePosition(Integer positionIndex, Mark nextMark, Mark previousMark){
this.positionIndex = positionIndex;
this.nextMark = nextMark;
this.previousMark = previousMark;
isFinishingLeg = false;
}
/**
* @return The position of the boat (0...number of marks in race - 1)
*/
public Integer getPositionIndex(){
return positionIndex;
}
/**
* @return The mark the boat is heading to
* will return NULL if this is the finishing legg
*/
public Mark getNextMark(){
return nextMark;
}
/**
* @return The mark the boat is heading away from,
* Will return NULL if this is the starting leg
*/
public Mark getPreviousMark(){
return previousMark;
}
/**
* Sets a flag that this is the last leg in the race
*/
public void setFinishingLeg(){
isFinishingLeg = true;
}
/**
* @return true if this is the last leg in the race
*/
public boolean getIsFinishingLeg() {
return isFinishingLeg;
}
}