diff --git a/src/main/java/seng302/gameServer/GameState.java b/src/main/java/seng302/gameServer/GameState.java index f055c3dd..4c7ec9eb 100644 --- a/src/main/java/seng302/gameServer/GameState.java +++ b/src/main/java/seng302/gameServer/GameState.java @@ -1,5 +1,7 @@ package seng302.gameServer; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.util.*; import javafx.application.Platform; @@ -9,10 +11,9 @@ import seng302.model.GeoPoint; import seng302.gameServer.server.messages.BoatActionType; import seng302.model.Player; import seng302.model.Yacht; -import seng302.gameServer.server.messages.BoatActionType; +import seng302.model.mark.MarkOrder; import seng302.model.mark.CompoundMark; import seng302.model.mark.Mark; -import seng302.model.mark.MarkOrder; import seng302.utilities.GeoUtility; /** @@ -21,6 +22,8 @@ import seng302.utilities.GeoUtility; */ public class GameState implements Runnable { + private Logger logger = LoggerFactory.getLogger(MarkOrder.class); + private static Integer STATE_UPDATES_PER_SECOND = 60; private static Long previousUpdateTime; @@ -32,6 +35,7 @@ public class GameState implements Runnable { private static Map yachts; private static Boolean isRaceStarted; private static GameStages currentStage; + private static MarkOrder markOrder; private static long startTime; private static Set marks; @@ -61,8 +65,9 @@ public class GameState implements Runnable { //set this when game stage changes to prerace previousUpdateTime = System.currentTimeMillis(); yachts = new HashMap<>(); + markOrder = new MarkOrder(); //This could be instantiated at some point with a select map? - new Thread(this).start(); + new Thread(this).start(); //Run the auto updates on the game state marks = new MarkOrder().getAllMarks(); } @@ -114,7 +119,11 @@ public class GameState implements Runnable { GameState.currentStage = currentStage; } - public static long getStartTime() { + public static MarkOrder getMarkOrder() { + return markOrder; + } + + public static long getStartTime(){ return startTime; } diff --git a/src/main/java/seng302/gameServer/server/simulator/parsers/CourseParser.java b/src/main/java/seng302/gameServer/server/simulator/parsers/CourseParser.java index 66def8a1..e4cbf676 100644 --- a/src/main/java/seng302/gameServer/server/simulator/parsers/CourseParser.java +++ b/src/main/java/seng302/gameServer/server/simulator/parsers/CourseParser.java @@ -79,18 +79,19 @@ public class CourseParser extends FileParser { if (node.getNodeType() == Node.ELEMENT_NODE) { Element e = (Element) node; Integer markID = Integer.valueOf(e.getAttribute("CompoundMarkID")); - String name = e.getAttribute("Name"); - CompoundMark cMark = new CompoundMark(markID, name); NodeList marks = e.getElementsByTagName("Mark"); - for (int i = 0; i < marks.getLength(); i++) { + List subMarks = new ArrayList<>(); + for (int i = 0; i < marks.getLength(); i++) { Mark mark = getMark(marks.item(i)); - if (mark != null) - cMark.addSubMarks(mark); + if (mark != null) { + subMarks.add(mark); + } } - return cMark; - } + + return new CompoundMark(markID, name, subMarks); + } System.out.println("Failed to create compound mark."); return null; } diff --git a/src/main/java/seng302/model/Yacht.java b/src/main/java/seng302/model/Yacht.java index 2eca35c3..8e566e4c 100644 --- a/src/main/java/seng302/model/Yacht.java +++ b/src/main/java/seng302/model/Yacht.java @@ -5,6 +5,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; @@ -33,12 +35,16 @@ public class Yacht extends Observable { 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 Double ROUNDING_DISTANCE = 50d; // TODO: 3/08/17 wmu16 - Look into this value further public static final Double MARK_COLLISION_DISTANCE = 15d; public static final Double YACHT_COLLISION_DISTANCE = 25.0; private static final Double BOUNCE_DISTANCE = 20.0; private static final Integer COLLISION_UPDATE_INTERVAL = 100; + + //BOTH AFAIK private String boatType; private Integer sourceId; @@ -48,11 +54,10 @@ public class Yacht extends Observable { 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; @@ -63,11 +68,13 @@ public class Yacht extends Observable { private GeoPoint location; private Integer boatStatus; private Double velocity; + //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; private Long lastCollisionUpdate; //CLIENT SIDE @@ -94,8 +101,9 @@ public class Yacht extends Observable { this.velocity = 0d; //in mms-1 this.hasEnteredRoundingZone = false; - this.hasPassedFirstLine = false; - this.hasPassedSecondLine = false; + this.hasPassedLine = false; + this.hasPassedThroughGate = false; + this.finishedRace = false; } public Mark markCollidedWith(){ @@ -169,9 +177,8 @@ public class Yacht extends Observable { } //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 @@ -204,14 +211,16 @@ public class Yacht extends Observable { /** - * 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); @@ -222,6 +231,144 @@ public class Yacht extends Observable { } } + + /** + * 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); + logger.debug(sourceId + " finished"); + // TODO: 8/08/17 wmu16 - Do something! + } + } + } + + public void adjustHeading(Double amount) { Double newVal = heading + amount; lastHeading = heading; @@ -300,7 +447,6 @@ public class Yacht extends Observable { } private void turnTowardsHeading(Double newHeading) { - System.out.println(newHeading); if (heading < 90 && newHeading > 270) { adjustHeading(-TURN_STEP); } else { @@ -432,14 +578,6 @@ public class Yacht extends Observable { this.lastMarkRounded = lastMarkRounded; } - public void setNextMark(CompoundMark nextMark) { - this.nextMark = nextMark; - } - - public CompoundMark getNextMark() { - return nextMark; - } - public GeoPoint getLocation() { return location; } @@ -504,8 +642,8 @@ public class Yacht extends Observable { 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 +656,20 @@ public class Yacht extends Observable { } } + private void logMarkRounding(CompoundMark currentMark) { + String typeString = "mark"; + if (currentMark.isGate()) { + typeString = "gate"; + } + logger.debug( + String.format("BoatID %d passed %s %s with id %d. Now on leg %d", + sourceId, + typeString, + currentMark.getMarks().get(0).getName(), + currentMark.getId(), + currentMarkSeqID)); + } + public void addLocationListener(YachtLocationListener listener) { locationListeners.add(listener); } diff --git a/src/main/java/seng302/model/mark/CompoundMark.java b/src/main/java/seng302/model/mark/CompoundMark.java index af4cd8a8..a4cc8d0c 100644 --- a/src/main/java/seng302/model/mark/CompoundMark.java +++ b/src/main/java/seng302/model/mark/CompoundMark.java @@ -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 marks = new ArrayList<>(); + private GeoPoint midPoint; - public CompoundMark(int markID, String name) { - this.compoundMarkId = markID; + public CompoundMark(int markID, String name, List marks) { + this.compoundMarkId = markID; this.name = name; - } - - public void addSubMarks(Mark... marks) { - this.marks.addAll(Arrays.asList(marks)); - } - - public void addSubMarks(List 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; diff --git a/src/main/java/seng302/model/mark/MarkOrder.java b/src/main/java/seng302/model/mark/MarkOrder.java index b2437957..d49f2d5b 100644 --- a/src/main/java/seng302/model/mark/MarkOrder.java +++ b/src/main/java/seng302/model/mark/MarkOrder.java @@ -21,7 +21,7 @@ import java.util.*; * Class to hold the order of the marks in the race. */ public class MarkOrder { - private List raceMarkOrder; + private List raceMarkOrder; private Logger logger = LoggerFactory.getLogger(MarkOrder.class); private Set allMarks; @@ -33,7 +33,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 getMarkOrder(){ + public List getMarkOrder(){ if (raceMarkOrder == null){ logger.warn("Race order accessed but not instantiated"); return null; @@ -43,26 +43,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); } public Set getAllMarks(){ @@ -74,7 +83,7 @@ public class MarkOrder { * @param xml An AC35 RaceXML * @return An ordered list of marks in the race */ - private List loadRaceOrderFromXML(String xml){ + private List loadRaceOrderFromXML(String xml){ DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); DocumentBuilder db; @@ -95,11 +104,11 @@ public class MarkOrder { logger.debug("Loaded RaceXML for mark order"); List corners = data.getMarkSequence(); Map marks = data.getCompoundMarks(); - List course = new ArrayList<>(); + List course = new ArrayList<>(); for (Corner corner : corners){ CompoundMark compoundMark = marks.get(corner.getCompoundMarkID()); - course.add(compoundMark.getMarks().get(0)); + course.add(compoundMark); allMarks.addAll(compoundMark.getMarks()); } @@ -109,17 +118,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 */ @@ -136,4 +134,4 @@ public class MarkOrder { } raceMarkOrder = loadRaceOrderFromXML(raceXML); } -} +} \ No newline at end of file diff --git a/src/main/java/seng302/model/mark/RacePosition.java b/src/main/java/seng302/model/mark/RacePosition.java deleted file mode 100644 index fc160b10..00000000 --- a/src/main/java/seng302/model/mark/RacePosition.java +++ /dev/null @@ -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; - } -} diff --git a/src/main/java/seng302/utilities/GeoUtility.java b/src/main/java/seng302/utilities/GeoUtility.java index 9aa61b9d..f6968601 100644 --- a/src/main/java/seng302/utilities/GeoUtility.java +++ b/src/main/java/seng302/utilities/GeoUtility.java @@ -45,6 +45,19 @@ public class GeoUtility { return (Math.toDegrees(getBearingRad(p1, p2)) + 360.0) % 360.0; } + + /** + * WARNING: this function DOES NOT account for wrapping around on lats / longs etc. + * SO BE CAREFUL IN USING THIS FUNCTION + * + * @param p1 GeoPoint 1 + * @param p2 GeoPoint 2 + * @return GeoPoint midPoint + */ + public static GeoPoint getDirtyMidPoint(GeoPoint p1, GeoPoint p2) { + return new GeoPoint((p1.getLat() + p2.getLat()) / 2, (p1.getLng() + p2.getLng()) / 2); + } + /** * Calculates the angle between to angular co-ordinates on a sphere in radians. * @@ -93,7 +106,6 @@ public class GeoUtility { return new GeoPoint(Math.toDegrees(endLat), Math.toDegrees(endLng)); } - /** * Performs the line function on two points of a line and a test point to test which side of the * line that point is on. If the return value is return 1, then the point is on one side of the @@ -125,6 +137,31 @@ public class GeoUtility { } } + /** + * Checks if the line formed by lastLocation and location doesn't intersect the line segment + * formed by mark1 and mark2 See the wiki Mark Rounding algorithm for more info + * + * @param mark1 One mark of the line + * @param mark2 The second mark of the line + * @param lastLocation The last location of the point crossing this line + * @param location The current location of the point crossing this line + * @return 0 if two line segment doesn't intersect, otherwise 1 if they intersect and + * lastLocation is on RHS of the line segment (mark1 to mark2) or 2 if lastLocation on LHS of + * the line segment (mark1 to mark2) + */ + public static Integer checkCrossedLine(GeoPoint mark1, GeoPoint mark2, GeoPoint lastLocation, + GeoPoint location) { + boolean enteredDirection = isClockwise(mark1, mark2, lastLocation); + boolean exitedDirection = isClockwise(mark1, mark2, location); + if (enteredDirection != exitedDirection) { + if (!isPointInTriangle(mark1, lastLocation, location, mark2) + && !isPointInTriangle(mark2, lastLocation, location, mark1)) { + + return enteredDirection ? 1 : 2; + } + } + return 0; + } /** * Given a point and a vector (angle and vector length) Will create a new point, that vector @@ -155,10 +192,24 @@ public class GeoUtility { * @param bearing2 the bearing of v2 * @return the difference of bearing from v1 to v2 */ - private static double getBearingDiff(double bearing1, double bearing2) { + private static Double getBearingDiff(double bearing1, double bearing2) { return ((360 - bearing1) + bearing2) % 360; } + /** + * Check if a geo point ins on the right hand side of the line segment, which + * formed by two geo points v1 to v2. (Algorithm: point is clockwise to the + * line if the bearing difference is less than 180 deg.) + * + * @param v1 one end of the line segment + * @param v2 another end of the line segment + * @param point the point to be tested + * @return true if the point is on the RHS of the line + */ + public static Boolean isClockwise(GeoPoint v1, GeoPoint v2, GeoPoint point) { + return getBearingDiff(getBearing(v1, v2), getBearing(v1, point)) < 180; + } + /** * Given three geo points to form a triangle, the method returns true if the fourth point is * inside the triangle @@ -169,15 +220,15 @@ public class GeoUtility { * @param point the point to be tested * @return true if the fourth point is inside the triangle */ - public static boolean isPointInTriangle(GeoPoint v1, GeoPoint v2, GeoPoint v3, GeoPoint point) { - // true, if diff of bearing from (v1->v2) to (v1->p) is less than 180 deg - boolean sideFlag = getBearingDiff(getBearing(v1, v2), getBearing(v1, point)) < 180; + public static Boolean isPointInTriangle(GeoPoint v1, GeoPoint v2, GeoPoint v3, GeoPoint point) { + // true, if diff of bearing from (v1 to v2) to (v1 to p) is less than 180 deg + boolean isCW = isClockwise(v1, v2, point); - if ((getBearingDiff(getBearing(v2, v3), getBearing(v2, point)) < 180) != sideFlag) { + if (isClockwise(v2, v3, point) != isCW) { return false; } - if ((getBearingDiff(getBearing(v3, v1), getBearing(v3, point)) < 180) != sideFlag) { + if (isClockwise(v3, v1, point) != isCW) { return false; } diff --git a/src/main/java/seng302/utilities/XMLParser.java b/src/main/java/seng302/utilities/XMLParser.java index c231535d..c4c4348b 100644 --- a/src/main/java/seng302/utilities/XMLParser.java +++ b/src/main/java/seng302/utilities/XMLParser.java @@ -256,9 +256,9 @@ public class XMLParser { if (cMarkNode.getNodeName().equals("CompoundMark")) { cMark = new CompoundMark( XMLParser.getNodeAttributeInt(cMarkNode, "CompoundMarkID"), - XMLParser.getNodeAttributeString(cMarkNode, "Name") + XMLParser.getNodeAttributeString(cMarkNode, "Name"), + createMarks(cMarkNode) ); - cMark.addSubMarks(createMarks(cMarkNode)); allMarks.add(cMark); } } diff --git a/src/main/java/seng302/visualiser/GameClient.java b/src/main/java/seng302/visualiser/GameClient.java index 5068889d..b33c68a8 100644 --- a/src/main/java/seng302/visualiser/GameClient.java +++ b/src/main/java/seng302/visualiser/GameClient.java @@ -178,7 +178,6 @@ public class GameClient { break; case BOAT_XML: -// System.out.println("GOT SUM BOATS YAY :)"); allBoatsMap = XMLParser.parseBoats( StreamParser.extractXmlMessage(packet) ); diff --git a/src/main/java/seng302/visualiser/controllers/RaceViewController.java b/src/main/java/seng302/visualiser/controllers/RaceViewController.java index d6bdf65c..8f5468b8 100644 --- a/src/main/java/seng302/visualiser/controllers/RaceViewController.java +++ b/src/main/java/seng302/visualiser/controllers/RaceViewController.java @@ -316,7 +316,7 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel updateRaceTime(); updateWindDirection(); updateOrder(); - updateSparkLine(); +// updateSparkLine(); } }, 0, 1000); } diff --git a/src/test/java/seng302/model/YachtTest.java b/src/test/java/seng302/model/YachtTest.java deleted file mode 100644 index 7937b43c..00000000 --- a/src/test/java/seng302/model/YachtTest.java +++ /dev/null @@ -1,54 +0,0 @@ -package seng302.model; - -import static org.junit.Assert.*; - -import org.junit.Before; -import org.junit.Test; -import seng302.model.mark.CompoundMark; -import seng302.model.mark.Mark; - -/** - * Use this link to test geo distances - * http://www.csgnetwork.com/gpsdistcalc.html - * Created by wmu16 on 3/08/17. - */ -public class YachtTest { - - private Yacht yacht; - private CompoundMark compoundMark; - private Double toleranceRatio = 0.01; - private GeoPoint p1 = new GeoPoint(57.670333, 11.827833); - private GeoPoint p2 = new GeoPoint(57.671524, 11.844495); - private GeoPoint p3 = new GeoPoint(57.670822, 11.843392); - private GeoPoint p4 = new GeoPoint(25.694829, 98.392049); - - @Before - public void setup() { - yacht = new Yacht("Yacht", - 0, - "0", - "WillIsCool", - "HaomingIsOk", - "NZL"); - - yacht.setLocation(57.670333, 11.827833); - - compoundMark = new CompoundMark(0, "HaomingsMark"); - Mark subMark1 = new Mark("H", 57.671524, 11.844495, 0); - Mark subMark2 = new Mark("H", 57.670822, 11.843392, 0); - compoundMark.addSubMarks(subMark1, subMark2); - - yacht.setNextMark(compoundMark); - } - - - @Test - public void testDistanceToNextMark() { - Double actual, expected; - actual = yacht.calcDistanceToNextMark(); - expected = 927d; - assertEquals(expected, actual, expected * toleranceRatio); - } - - -} \ No newline at end of file diff --git a/src/test/java/seng302/model/mark/CompoundMarkTest.java b/src/test/java/seng302/model/mark/CompoundMarkTest.java new file mode 100644 index 00000000..55ccebf3 --- /dev/null +++ b/src/test/java/seng302/model/mark/CompoundMarkTest.java @@ -0,0 +1,66 @@ +package seng302.model.mark; + +import static org.junit.Assert.*; + +import java.util.ArrayList; +import java.util.List; +import org.junit.Before; +import org.junit.Test; +import seng302.model.GeoPoint; + +/** + * A class to test the compound mark calss + * Created by wmu16 on 10/08/17. + */ +public class CompoundMarkTest { + + private Mark mark1; + private Mark mark2; + private CompoundMark gateMark; + private CompoundMark singleMark; + + private static Double TOLERANCE_RATIO = 0.01; + + + @Before + public void setUp() throws Exception { + mark1 = new Mark("Mark1", 57.670333, 11.842833, 0); + mark2 = new Mark("Mark2", 57.671524, 11.844495, 1); + + List gateMarks = new ArrayList(); + gateMarks.add(mark1); + gateMarks.add(mark2); + + List singleMarks = new ArrayList(); + singleMarks.add(mark1); + + gateMark = new CompoundMark(0, "Fun Mark", gateMarks); + singleMark = new CompoundMark(1, "Awesome Mark", singleMarks); + } + + + @Test + public void getSubMark() throws Exception { + assertEquals(mark1, gateMark.getSubMark(1)); + assertEquals(mark2, gateMark.getSubMark(2)); + + assertEquals(mark1, singleMark.getSubMark(1)); + } + + @Test + public void getMidPoint() throws Exception { + GeoPoint result = gateMark.getMidPoint(); + assertEquals(57.6709285, result.getLat(), result.getLat() * TOLERANCE_RATIO); + assertEquals(11.843664, result.getLng(), result.getLng() * TOLERANCE_RATIO); + + result = singleMark.getMidPoint(); + assertEquals(result, mark1); + } + + @Test + public void isGate() throws Exception { + assertTrue(gateMark.isGate()); + assertFalse(singleMark.isGate()); + } + +} \ No newline at end of file diff --git a/src/test/java/seng302/models/MarkOrderTest.java b/src/test/java/seng302/models/MarkOrderTest.java index 8db06ec8..42531563 100644 --- a/src/test/java/seng302/models/MarkOrderTest.java +++ b/src/test/java/seng302/models/MarkOrderTest.java @@ -3,19 +3,22 @@ package seng302.models; import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.Test; +import seng302.model.mark.CompoundMark; import seng302.model.mark.Mark; import seng302.model.mark.MarkOrder; -import seng302.model.mark.RacePosition; import static junit.framework.TestCase.assertEquals; +import static junit.framework.TestCase.assertFalse; import static junit.framework.TestCase.assertTrue; public class MarkOrderTest { private static MarkOrder markOrder; + private static Integer currentSeqID; @BeforeClass public static void setup(){ markOrder = new MarkOrder(); + currentSeqID = 0; } /** @@ -26,54 +29,39 @@ public class MarkOrderTest { assertTrue(markOrder != null); } - /** - * Test if .getNextMark() returns null if it is called with the final mark in the race - */ + @Test - public void testNextMarkAtEnd(){ - // There are no marks in the XML, therefore this can't be tested - if (markOrder.getMarkOrder().size() == 0){ - return; - } + public void testIsLastMark() { + currentSeqID = 0; + assertFalse(markOrder.isLastMark(currentSeqID)); - Mark lastMark = markOrder.getMarkOrder().get(markOrder.getMarkOrder().size() - 1); - Integer lastIndex = markOrder.getMarkOrder().size() - 1; - - RacePosition lastRacePosition = new RacePosition(lastIndex, lastMark, null); - - assertEquals(null, markOrder.getNextPosition(lastRacePosition).getNextMark()); + currentSeqID = markOrder.getMarkOrder().size() - 1; + assertTrue(markOrder.isLastMark(currentSeqID)); } - /** - * Test if .getNextMark() method returns the next mark in the race - */ @Test - public void testNextMark(){ - // There are not enough marks for this to be tested - if (markOrder.getMarkOrder().size() < 2){ - return; - } + public void testGetNextMark() { + currentSeqID = 4; + CompoundMark nextMark = markOrder.getMarkOrder().get(4 + 1); + assertEquals(nextMark, markOrder.getNextMark(currentSeqID)); - RacePosition firstRacePos = new RacePosition(0, markOrder.getMarkOrder().get(0), null); - - assertEquals(markOrder.getMarkOrder().get(1).getName(), markOrder.getNextPosition(firstRacePos).getNextMark().getName()); + currentSeqID = 3; + nextMark = markOrder.getMarkOrder().get(3 + 1); + assertEquals(nextMark, markOrder.getNextMark(currentSeqID)); } - /** - * Test if a whole race can be completed - */ @Test - public void testMarkSequence(){ - RacePosition current = markOrder.getFirstPosition(); + public void testGetCurrentMark() { + currentSeqID = 0; + CompoundMark currentMark = markOrder.getMarkOrder().get(0); + assertEquals(currentMark, markOrder.getCurrentMark(0)); + } - while (!current.getIsFinishingLeg()){ - - current = markOrder.getNextPosition(current); - - if (current.getIsFinishingLeg()){ - assertEquals(null, current.getNextMark()); - } - } + @Test + public void testGetPreviousMark() { + currentSeqID = 1; + CompoundMark prevMark = markOrder.getMarkOrder().get(0); + assertEquals(prevMark, markOrder.getPreviousMark(currentSeqID)); } @AfterClass diff --git a/src/test/java/seng302/utilities/GeoUtilityTest.java b/src/test/java/seng302/utilities/GeoUtilityTest.java index 8382bb8c..ca2af0e9 100644 --- a/src/test/java/seng302/utilities/GeoUtilityTest.java +++ b/src/test/java/seng302/utilities/GeoUtilityTest.java @@ -6,12 +6,11 @@ import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertTrue; import javafx.geometry.Point2D; -import org.junit.Before; import org.junit.Test; import seng302.model.GeoPoint; -import seng302.utilities.GeoUtility; /** + * http://www.geoplaner.com/ For plotting geo points for visualisation * To test methods in GeoUtility. * Use this site to calculate distances * https://rechneronline.de/geo-coordinates/#distance @@ -150,4 +149,32 @@ public class GeoUtilityTest { assertFalse(GeoUtility.isPointInTriangle(v1, v2, v3, p2)); } + + + @Test + public void testCheckCrossedGate() { + GeoPoint mark1 = new GeoPoint(37.40937, -122.62233); + GeoPoint mark2 = new GeoPoint(37.40938, -122.62154); + GeoPoint location1 = new GeoPoint(37.40964, -122.62196); + GeoPoint location2 = new GeoPoint(37.40910, -122.62189); + GeoPoint location3 = new GeoPoint(37.40949, -122.62202); + GeoPoint location4 = new GeoPoint(37.40927, -122.62152); + + // M1 -> M3 enters from CCW side + assertTrue(GeoUtility.checkCrossedLine(mark1, mark2, location1, location2) == 2); + // M1 -> M3 doesn't across + assertFalse(GeoUtility.checkCrossedLine(mark1, mark2, location1, location3) > 0); + // M2 -> M3 enters from CW side + assertTrue(GeoUtility.checkCrossedLine(mark1, mark2, location2, location3) == 1); + // order changes intersect direction + assertTrue(GeoUtility.checkCrossedLine(mark2, mark1, location2, location3) == 2); + assertTrue(GeoUtility.checkCrossedLine(mark1, mark2, location3, location2) == 2); + } + + @Test + public void testDirtyMiddlePoint() { + GeoPoint result = GeoUtility.getDirtyMidPoint(p1, p2); + assertEquals(57.6709285, result.getLat(), result.getLat() * toleranceRate); + assertEquals(11.836164, result.getLng(), result.getLng() * toleranceRate); + } } \ No newline at end of file