From 824a5ed3a2194ce5201a54e791b476c18ffec73b Mon Sep 17 00:00:00 2001 From: fjc40 Date: Tue, 23 May 2017 18:35:39 +1200 Subject: [PATCH 01/11] Merge branch 'boat_boundarty_fix' into story36 # Conflicts: # mock/src/main/java/seng302/Model/Race.java Resolved. --- mock/src/main/java/seng302/Model/Angle.java | 3 - .../java/seng302/Model/GPSCoordinate.java | 223 ++++++++++++++++++ mock/src/main/java/seng302/Model/Race.java | 45 ++-- .../Networking/Messages/BoatLocation.java | 2 +- .../Networking/Messages/RaceStatus.java | 2 +- 5 files changed, 254 insertions(+), 21 deletions(-) diff --git a/mock/src/main/java/seng302/Model/Angle.java b/mock/src/main/java/seng302/Model/Angle.java index 0fa6db0c..8fa767dd 100644 --- a/mock/src/main/java/seng302/Model/Angle.java +++ b/mock/src/main/java/seng302/Model/Angle.java @@ -54,9 +54,6 @@ public class Angle implements Comparable { return this.degrees; } - public void setDegrees(double degrees) { - this.degrees = degrees; - } /** * Returns the value of this Angle object, in radians. diff --git a/mock/src/main/java/seng302/Model/GPSCoordinate.java b/mock/src/main/java/seng302/Model/GPSCoordinate.java index 3f9527e2..a185b865 100644 --- a/mock/src/main/java/seng302/Model/GPSCoordinate.java +++ b/mock/src/main/java/seng302/Model/GPSCoordinate.java @@ -1,10 +1,12 @@ package seng302.Model; +import javafx.util.Pair; import org.geotools.referencing.GeodeticCalculator; import org.opengis.geometry.DirectPosition; import seng302.Constants; import java.awt.geom.Point2D; +import java.util.ArrayList; import java.util.Comparator; import java.util.List; @@ -292,5 +294,226 @@ public class GPSCoordinate { } + + /** + * Takes a list of GPS coordinates describing a course boundary, and "shrinks" it inwards by 50m. + * @param boundary The boundary of course. + * @return A copy of the course boundary list, shrunk inwards by 50m. + */ + public static List getShrinkBoundary(List boundary) { + + + double shrinkDistance = 50d; + List shrunkBoundary = new ArrayList<>(boundary.size()); + //This is a list of edges that have been shrunk/shifted inwards. + List> shrunkEdges = new ArrayList<>(); + + + //We need to invert some of our opertations depending if the boundary is clockwise or anti-clockwise. + boolean isClockwise = GPSCoordinate.isClockwisePolygon(boundary); + double clockwiseScaleFactor = 0; + + if (isClockwise) { + clockwiseScaleFactor = 1; + } else { + clockwiseScaleFactor = -1; + } + + + /** + * Starting at a vertex, face anti-clockwise along an adjacent edge. + Replace the edge with a new, parallel edge placed at distance d to the "left" of the old one. + Repeat for all edges. + Find the intersections of the new edges to get the new vertices. + Detect if you've become a crossed polynomial and decide what to do about it. Probably add a new vertex at the crossing-point and get rid of some old ones. I'm not sure whether there's a better way to detect this than just to compare every pair of non-adjacent edges to see if their intersection lies between both pairs of vertices. + */ + + //For the first (size-1) adjacent pairs. + for (int i = 0; i < (boundary.size() - 1); i++) { + + //Get the points. + GPSCoordinate firstPoint = boundary.get(i); + GPSCoordinate secondPoint = boundary.get(i + 1); + + //Get the bearing between two adjacent points. + Bearing bearing = GPSCoordinate.calculateBearing(firstPoint, secondPoint); + + //Calculate angle perpendicular to bearing. + Bearing perpindicularBearing = Bearing.fromDegrees(bearing.degrees() + (90d * clockwiseScaleFactor)); + + //Translate both first and second point by 50m, using this bearing. These form our inwards shifted edge. + GPSCoordinate firstPointTranslated = GPSCoordinate.calculateNewPosition(firstPoint, shrinkDistance, Azimuth.fromBearing(perpindicularBearing)); + GPSCoordinate secondPointTranslated = GPSCoordinate.calculateNewPosition(secondPoint, shrinkDistance, Azimuth.fromBearing(perpindicularBearing)); + + //Add edge to list. + shrunkEdges.add(new Pair<>(firstPointTranslated, secondPointTranslated)); + + } + + //For the final adjacent pair, between the last and first point. + //Get the points. + GPSCoordinate firstPoint = boundary.get(boundary.size() - 1); + GPSCoordinate secondPoint = boundary.get(0); + + //Get the bearing between two adjacent points. + Bearing bearing = GPSCoordinate.calculateBearing(firstPoint, secondPoint); + + //Calculate angle perpendicular to bearing. + Bearing perpindicularBearing = Bearing.fromDegrees(bearing.degrees() + (90d * clockwiseScaleFactor)); + + //Translate both first and second point by 50m, using this bearing. These form our inwards shifted edge. + GPSCoordinate firstPointTranslated = GPSCoordinate.calculateNewPosition(firstPoint, shrinkDistance, Azimuth.fromBearing(perpindicularBearing)); + GPSCoordinate secondPointTranslated = GPSCoordinate.calculateNewPosition(secondPoint, shrinkDistance, Azimuth.fromBearing(perpindicularBearing)); + + //Add edge to list. + shrunkEdges.add(new Pair<>(firstPointTranslated, secondPointTranslated)); + + + //We now have a list of edges that have been shifted inwards. + //We need to find the intersections between adjacent vertices in our edge list. E.g., intersection between edge1-right, and edge2-left. + + //For the first (size-1) adjacent pairs. + for (int i = 0; i < (shrunkEdges.size() - 1); i++) { + + //Get the pair of adjacent edges. + Pair edge1 = shrunkEdges.get(i); + Pair edge2 = shrunkEdges.get(i + 1); + + //Get the x and y coordinates of first edge. + double x1 = edge1.getKey().getLongitude(); + double x2 = edge1.getValue().getLongitude(); + double y1 = edge1.getKey().getLatitude(); + double y2 = edge1.getValue().getLatitude(); + + //Get the x and y coordinates of second edge. + double x3 = edge2.getKey().getLongitude(); + double x4 = edge2.getValue().getLongitude(); + double y3 = edge2.getKey().getLatitude(); + double y4 = edge2.getValue().getLatitude(); + + //Find the equations for both edges. + // y = a*x + b + //First equation. + double a1 = (y2 - y1) / (x2 - x1); + double b1 = y1 - a1 * x1; + + //Second equation. + double a2 = (y4 - y3) / (x4 - x3); + double b2 = y3 - a2 * x3; + + + //Find intersecting x coordinate. + // a1 * x + b1 = a2 * x + b2 + double x0 = -(b1 - b2) / (a1 - a2); + //Find intersecting y coordinate. + double y0 = x0 * a1 + b1; + + //Add this to shrunk boundary list. + GPSCoordinate coordinate = new GPSCoordinate(y0, x0); + shrunkBoundary.add(coordinate); + + } + + + //For the final adjacent pair, between the last and first point. + //Get the pair of adjacent edges. + Pair edge1 = shrunkEdges.get(shrunkEdges.size() - 1); + Pair edge2 = shrunkEdges.get(0); + + //Get the x and y coordinates of first edge. + double x1 = edge1.getKey().getLongitude(); + double x2 = edge1.getValue().getLongitude(); + double y1 = edge1.getKey().getLatitude(); + double y2 = edge1.getValue().getLatitude(); + + //Get the x and y coordinates of second edge. + double x3 = edge2.getKey().getLongitude(); + double x4 = edge2.getValue().getLongitude(); + double y3 = edge2.getKey().getLatitude(); + double y4 = edge2.getValue().getLatitude(); + + //Find the equations for both edges. + // y = a*x + b + //First equation. + double a1 = (y2 - y1) / (x2 - x1); + double b1 = y1 - a1 * x1; + + //Second equation. + double a2 = (y4 - y3) / (x4 - x3); + double b2 = y3 - a2 * x3; + + + //Find intersecting x coordinate. + // a1 * x + b1 = a2 * x + b2 + double x0 = -(b1 - b2) / (a1 - a2); + //Find intersecting y coordinate. + double y0 = x0 * a1 + b1; + + //Add this to shrunk boundary list. + GPSCoordinate coordinate = new GPSCoordinate(y0, x0); + shrunkBoundary.add(coordinate); + + + + return shrunkBoundary; + + } + + + /** + * Determines if a list of coordinates describes a boundary polygon in clockwise or anti-clockwise order. + * @param boundary The list of coodinates. + * @return True if clockwise, false if anti-clockwise. + */ + public static boolean isClockwisePolygon(List boundary) { + + /** From https://stackoverflow.com/questions/1165647/how-to-determine-if-a-list-of-polygon-points-are-in-clockwise-order + * sum all pairs (x2 − x1)(y2 + y1) + point[0] = (5,0) edge[0]: (6-5)(4+0) = 4 + point[1] = (6,4) edge[1]: (4-6)(5+4) = -18 + point[2] = (4,5) edge[2]: (1-4)(5+5) = -30 + point[3] = (1,5) edge[3]: (1-1)(0+5) = 0 + point[4] = (1,0) edge[4]: (5-1)(0+0) = 0 + --- + -44 counter-clockwise + */ + + double sum = 0; + + //For the first (size-1) adjacent pairs. + for (int i = 0; i < (boundary.size() - 1); i++) { + + //Get the points. + GPSCoordinate firstPoint = boundary.get(i); + GPSCoordinate secondPoint = boundary.get(i + 1); + + double xDelta = secondPoint.getLongitude() - firstPoint.getLongitude(); + double ySum = secondPoint.getLatitude() + firstPoint.getLatitude(); + + double product = xDelta * ySum; + + sum += product; + + } + + //For the final adjacent pair, between the last and first point. + //Get the points. + GPSCoordinate firstPoint = boundary.get(boundary.size() - 1); + GPSCoordinate secondPoint = boundary.get(0); + + double xDelta = secondPoint.getLongitude() - firstPoint.getLongitude(); + double ySum = secondPoint.getLatitude() + firstPoint.getLatitude(); + + double product = xDelta * ySum; + + sum += product; + + + //sum > 0 is clockwise, sum < 0 is anticlockwise. + return sum > 0; + + + } + } diff --git a/mock/src/main/java/seng302/Model/Race.java b/mock/src/main/java/seng302/Model/Race.java index f0810eaf..1a9b447d 100644 --- a/mock/src/main/java/seng302/Model/Race.java +++ b/mock/src/main/java/seng302/Model/Race.java @@ -46,6 +46,11 @@ public class Race implements Runnable { */ private List boundary; + /** + * A copy of the boundary list, except "shrunk" inwards by 50m. + */ + private List shrinkBoundary; + /** * The elapsed time, in milliseconds, of the race. */ @@ -61,7 +66,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 = 20ms, scale = 5, frame represents 20 * 5 = 100ms, and so boats are simulated for 100ms, even though only 20ms actually occurred. */ - private int scaleFactor = 5; + private int scaleFactor = 30; /** * The race ID of the course. @@ -119,6 +124,9 @@ 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())); @@ -589,21 +597,15 @@ public class Race implements Runnable { 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); + } else { + //We also need to use the new VMG if our current bearing will take us out of the course. + if (!this.checkBearingInsideCourse(boat.getBearing(), boat.getCurrentPosition())) { + boat.setVMG(newVMG); + } + } @@ -630,6 +632,8 @@ public class Race implements Runnable { boolean foundAnyBadBearing = false; boolean foundLowerBearing = false; + boolean foundAnyGoodBearing = false; + boolean stopFindingUpperBearing = false; //Check all bearings between [0, 360). for (double angle = 0; angle < 360; angle += 1) { @@ -646,11 +650,14 @@ public class Race implements Runnable { if (foundAnyBadBearing && !foundLowerBearing) { lowerBearing = bearing; foundLowerBearing = true; + foundAnyGoodBearing = true; } //The upper bearing will be the last acceptable bearing before finding any unacceptable bearing. if (!foundAnyBadBearing) { upperBearing = bearing; + } else if (foundAnyBadBearing && foundAnyGoodBearing && !stopFindingUpperBearing) { + upperBearing = bearing; } @@ -658,11 +665,16 @@ public class Race implements Runnable { foundAnyBadBearing = true; + if (foundAnyBadBearing && foundAnyGoodBearing) { + stopFindingUpperBearing = true; + } + } } + //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; @@ -683,11 +695,11 @@ public class Race implements Runnable { //Tests to see if a point in front of the boat is out of bounds. - double epsilonMeters = 100d; + double epsilonMeters = 50d; GPSCoordinate testCoord = GPSCoordinate.calculateNewPosition(position, epsilonMeters, azimuth); //If it isn't inside the boundary, calculate new bearing. - if (GPSCoordinate.isInsideBoundary(testCoord, this.boundary)) { + if (GPSCoordinate.isInsideBoundary(testCoord, this.shrinkBoundary)) { return true; } else { return false; @@ -865,6 +877,7 @@ public class Race implements Runnable { if (windDir < 0){ windDir += 65535; } + this.windDirection = new Bearing(BoatLocation.convertHeadingIntToDouble(windDir)); } @@ -884,4 +897,4 @@ public class Race implements Runnable { protected int getWind(){ return windDir; } -} \ No newline at end of file +} diff --git a/network/src/main/java/seng302/Networking/Messages/BoatLocation.java b/network/src/main/java/seng302/Networking/Messages/BoatLocation.java index 9cb0bc78..fe7f2161 100644 --- a/network/src/main/java/seng302/Networking/Messages/BoatLocation.java +++ b/network/src/main/java/seng302/Networking/Messages/BoatLocation.java @@ -227,7 +227,7 @@ public class BoatLocation extends AC35Data { */ public static int convertHeadingDoubleToInt(double heading) { - int headingInt = (int) ((heading / 360.0) * 65536.0); + int headingInt = (int) ((heading * 65536.0) / 360.0); return headingInt; } diff --git a/network/src/main/java/seng302/Networking/Messages/RaceStatus.java b/network/src/main/java/seng302/Networking/Messages/RaceStatus.java index 89c7b90c..599411ac 100644 --- a/network/src/main/java/seng302/Networking/Messages/RaceStatus.java +++ b/network/src/main/java/seng302/Networking/Messages/RaceStatus.java @@ -18,7 +18,7 @@ public class RaceStatus extends AC35Data { private int windSpeed; private int raceType; private List boatStatuses; - private static final double windDirectionScalar = 360.0 / 32768.0; // 0x8000 / 360 + private static final double windDirectionScalar = 360.0 / 65536.0; // 0x8000 / 360 public RaceStatus(long currentTime, int raceID, int raceStatus, long expectedStartTime, int windDirection, int windSpeed, int raceType, List boatStatuses){ super(MessageType.RACESTATUS); From 7f3500202ffca4d148086943044e812745da5e3b Mon Sep 17 00:00:00 2001 From: fjc40 Date: Wed, 24 May 2017 19:20:30 +1200 Subject: [PATCH 02/11] Mock.Polars: Moved some code into isBearingInsideInterval and isFlippedInterval utility functions. Mock.Race: Appear to have fixed the boats going out bounds issue at last, by fixing a few bugs, simplifying the code, and removing the boat turning speeds. --- mock/src/main/java/seng302/Model/Polars.java | 74 ++++++++++---- mock/src/main/java/seng302/Model/Race.java | 100 +++++++++---------- 2 files changed, 101 insertions(+), 73 deletions(-) diff --git a/mock/src/main/java/seng302/Model/Polars.java b/mock/src/main/java/seng302/Model/Polars.java index 1e3251f3..cf7d448d 100644 --- a/mock/src/main/java/seng302/Model/Polars.java +++ b/mock/src/main/java/seng302/Model/Polars.java @@ -118,10 +118,7 @@ public class Polars { //If the lower bound is greater than the upper bound, we have a "flipped" interval. That is for, e.g., [70, 55] the lower bound is greater than the upper bound, and so it checks that (VMGAngle >= 70 OR VMGAngle =< 55), instead of (VMGAngle >= 70 AND VMGAngle =< 55). - boolean flippedInterval = false; - if (bearingLowerBound.degrees() > bearingUpperBound.degrees()) { - flippedInterval = true; - } + boolean flippedInterval = Polars.isFlippedInterval(bearingLowerBound, bearingUpperBound); @@ -179,7 +176,7 @@ public class Polars { Bearing bestVMGAngle = Bearing.fromDegrees(0d); //Calculate the VMG for all possible angles at this wind speed. - for (double angleDegree = 0; angleDegree < 360; angleDegree += 0.1) { + for (double angleDegree = 0; angleDegree < 360; angleDegree += 1) { Bearing angle = Bearing.fromDegrees(angleDegree); //This is the true bearing of the boat, if it went at the angle against the wind. @@ -191,22 +188,11 @@ public class Polars { //Check that the boat's bearing would actually be acceptable. //We continue (skip to next iteration) if it is outside of the interval. - if (flippedInterval) { - //Bearing must be inside [lower, upper], where lower > upper. So, bearing must be >= lower, or bearing < upper. We use inverted logic since we are skipping if it is true. - if ((trueBoatBearing.degrees() < bearingLowerBound.degrees()) & (trueBoatBearing.degrees() > bearingUpperBound.degrees())) { - continue; - } - - } else { - //Bearing must be inside [lower, upper]. - if ((trueBoatBearing.degrees() < bearingLowerBound.degrees()) || (trueBoatBearing.degrees() > bearingUpperBound.degrees())) { - continue; - } - + if (!Polars.isBearingInsideInterval(trueBoatBearing, bearingLowerBound, bearingUpperBound)) { + continue; } - //Basic linear interpolation. Find the nearest two angles from the table, and interpolate between them. //Check which pair of adjacent angles the angle is between. @@ -313,6 +299,58 @@ public class Polars { } + /** + * Determines whether an interval is "flipped". This means that the lower bound is greater than the upper bound (e.g., [290, 43] degrees). + * @param lowerBound The lower bound. + * @param upperBound The upper bound. + * @return True if the interval is flipped, false otherwise. + */ + public static boolean isFlippedInterval(Bearing lowerBound, Bearing upperBound) { + + //If the lower bound is greater than the upper bound, we have a "flipped" interval. + boolean flippedInterval = false; + if (lowerBound.degrees() > upperBound.degrees()) { + flippedInterval = true; + } + + return flippedInterval; + } + + + /** + * Determines if a bearing is inside an interval. + * @param bearing The bearing to check. + * @param lowerBound The lower bound of the interval. + * @param upperBound The upper bound of the interval. + * @return True if the bearing is inside the interval, false otherwise. + */ + public static boolean isBearingInsideInterval(Bearing bearing, Bearing lowerBound, Bearing upperBound) { + + //Check if it's a flipped interval. + boolean flippedInterval = Polars.isFlippedInterval(lowerBound, upperBound); + + if (flippedInterval) { + //Bearing must be inside [lower, upper], where lower > upper. So, bearing must be >= lower, or bearing < upper. We use inverted logic since we are skipping if it is true. + if ((bearing.degrees() >= lowerBound.degrees()) || (bearing.degrees() <= upperBound.degrees())) { + return true; + } else { + return false; + } + + } else { + //Bearing must be inside [lower, upper]. + if ((bearing.degrees() >= lowerBound.degrees()) && (bearing.degrees() <= upperBound.degrees())) { + return true; + } else { + return false; + } + + } + + + } + + /** * Calculate the linear interpolation scalar for a value between two bounds. E.g., lower = 7, upper = 10, value = 8, therefore the scalar (or the proportion between the bounds) is 0.333. * Also assumes that the bounds are periodic - e.g., for angles a lower bound of 350deg and upper bound of 5deg is in interval of 15 degrees. diff --git a/mock/src/main/java/seng302/Model/Race.java b/mock/src/main/java/seng302/Model/Race.java index 1a9b447d..d46840f5 100644 --- a/mock/src/main/java/seng302/Model/Race.java +++ b/mock/src/main/java/seng302/Model/Race.java @@ -502,67 +502,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() - boat.calculateBearingToNextMarker().degrees()); + Angle angleBetweenDestAndHeading = Angle.fromDegrees(currentVMG.getBearing().degrees() - bearingToDestination.degrees()); //Calculates the angle between the new VMG and the boat's destination. - Angle angleBetweenDestAndNewVMG = Angle.fromDegrees(vmg.getBearing().degrees() - boat.calculateBearingToNextMarker().degrees()); + Angle angleBetweenDestAndNewVMG = Angle.fromDegrees(potentialVMG.getBearing().degrees() - bearingToDestination.degrees()); //Calculate the boat's current velocity. - double currentVelocity = Math.cos(angleBetweenDestAndHeading.radians()) * boat.getCurrentSpeed(); + double currentVelocity = Math.cos(angleBetweenDestAndHeading.radians()) * currentVMG.getSpeed(); //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. @@ -593,6 +590,7 @@ public class Race implements Runnable { //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); @@ -626,14 +624,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; - boolean foundAnyGoodBearing = false; - boolean stopFindingUpperBearing = false; + + double lastAngle = -1; + boolean lastAngleWasGood = false; //Check all bearings between [0, 360). for (double angle = 0; angle < 360; angle += 1) { @@ -644,36 +641,28 @@ 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; - foundAnyGoodBearing = true; - } + if (lastAngle != -1) { - //The upper bearing will be the last acceptable bearing before finding any unacceptable bearing. - if (!foundAnyBadBearing) { - upperBearing = bearing; - } else if (foundAnyBadBearing && foundAnyGoodBearing && !stopFindingUpperBearing) { - 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 (foundAnyBadBearing && foundAnyGoodBearing) { - stopFindingUpperBearing = 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; @@ -687,6 +676,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) { From 92e5dc4490b2fbbd8336ab76ec28d281a3124e35 Mon Sep 17 00:00:00 2001 From: Erika Savell Date: Thu, 25 May 2017 01:14:54 +1200 Subject: [PATCH 03/11] Cherrypicked changes allowing vis to display est time from bugged branch to current master. #story[875] --- .../seng302/Controllers/StartController.java | 1 - .../main/java/seng302/Mock/StreamedRace.java | 42 +++++++++++++------ .../src/main/java/seng302/Model/Boat.java | 18 ++++++++ .../seng302/Model/ResizableRaceCanvas.java | 14 +++++-- .../main/java/seng302/VisualiserInput.java | 4 ++ 5 files changed, 62 insertions(+), 17 deletions(-) diff --git a/visualiser/src/main/java/seng302/Controllers/StartController.java b/visualiser/src/main/java/seng302/Controllers/StartController.java index 87996f76..db8091ff 100644 --- a/visualiser/src/main/java/seng302/Controllers/StartController.java +++ b/visualiser/src/main/java/seng302/Controllers/StartController.java @@ -98,7 +98,6 @@ public class StartController extends Controller implements Observer { raceStat = visualiserInput.getRaceStatus().getRaceStatus(); raceStatusLabel.setText("Race Status: " + visualiserInput.getRaceStatus().getRaceStatus()); if (raceStat==2 || raceStat == 3) { - System.out.println("countdown finished!");//TEMP DEBUG REMOVE stop(); startWrapper.setVisible(false); diff --git a/visualiser/src/main/java/seng302/Mock/StreamedRace.java b/visualiser/src/main/java/seng302/Mock/StreamedRace.java index 0b867f4d..71b9c68a 100644 --- a/visualiser/src/main/java/seng302/Mock/StreamedRace.java +++ b/visualiser/src/main/java/seng302/Mock/StreamedRace.java @@ -114,16 +114,17 @@ public class StreamedRace implements Runnable { * Updates the boat's gps coordinates * * @param boat to be updated - * @param millisecondsElapsed time since last update */ - private void updatePosition(Boat boat, int millisecondsElapsed) { + private void updatePosition(Boat boat) { int sourceID = boat.getSourceID(); BoatLocation boatLocation = visualiserInput.getBoatLocationMessage(sourceID); + BoatStatus boatStatus = visualiserInput.getBoatStatusMessage(sourceID); if(boatLocation != null) { double lat = boatLocation.getLatitudeDouble(); double lon = boatLocation.getLongitudeDouble(); boat.setCurrentPosition(new GPSCoordinate(lat, lon)); boat.setHeading(boatLocation.getHeadingDegrees()); + boat.setEstTime(convertEstTime(boatStatus.getEstTimeAtNextMark(), boatLocation.getTime())); double MMPS_TO_KN = 0.001944; boat.setVelocity(boatLocation.getBoatSOG() * MMPS_TO_KN); } @@ -198,19 +199,20 @@ public class StreamedRace implements Runnable { public void handle(long arg0) { totalTimeElapsed = System.currentTimeMillis() - timeRaceStarted; - for (Boat boat : startingBoats) { - if (boat != null && !boat.isFinished()) { - updatePosition(boat, Math.round(1000 / lastFPS) > 20 ? 15 : Math.round(1000 / lastFPS)); - checkPosition(boat, totalTimeElapsed); + for (Boat boat : startingBoats) { + if (boat != null && !boat.isFinished()) { + updatePosition(boat); + checkPosition(boat, totalTimeElapsed); + } + } - } - for (Marker mark: boatMarkers){ - if (mark != null){ - updateMarker(mark); + for (Marker mark: boatMarkers){ + if (mark != null){ + updateMarker(mark); + } } - } - //System.out.println(boatsFinished + ":" + startingBoats.size()); - if (visualiserInput.getRaceStatus().isFinished()){ + + if (visualiserInput.getRaceStatus().isFinished()) { controller.finishRace(startingBoats); stop(); } @@ -258,4 +260,18 @@ public class StreamedRace implements Runnable { return startingBoats; } + /** + * Takes an estimated time an event will occur, and converts it to the number of seconds before the event will occur. + * + * @param estTimeMillis + * @return int difference between time the race started and the estimated time + */ + private int convertEstTime(long estTimeMillis, long currentTime) { + + long estElapsedMillis = estTimeMillis - currentTime; + int estElapsedSecs = Math.round(estElapsedMillis/1000); + return estElapsedSecs; + + } + } diff --git a/visualiser/src/main/java/seng302/Model/Boat.java b/visualiser/src/main/java/seng302/Model/Boat.java index a99ed148..c9765107 100644 --- a/visualiser/src/main/java/seng302/Model/Boat.java +++ b/visualiser/src/main/java/seng302/Model/Boat.java @@ -29,6 +29,7 @@ public class Boat { private boolean started = false; private boolean dnf = false; private int sourceID; + private int estTime; private final Queue track = new ConcurrentLinkedQueue<>(); private long nextValidTime = 0; @@ -248,4 +249,21 @@ public class Boat { public void setTimeSinceLastMark(ZonedDateTime timeSinceLastMark) { this.timeSinceLastMark = timeSinceLastMark; } + + public void setEstTime(int estTime) { this.estTime = estTime; } + + public String getFormattedEstTime() { + if (estTime < 0) { + return " -"; + } + if (estTime <= 60) { + return " " + estTime + "s"; + } else { + int seconds = estTime % 60; + int minutes = (estTime - seconds) / 60; + return String.format(" %dm %ds", minutes, seconds); + } + + + } } diff --git a/visualiser/src/main/java/seng302/Model/ResizableRaceCanvas.java b/visualiser/src/main/java/seng302/Model/ResizableRaceCanvas.java index 5c26b293..04460b28 100644 --- a/visualiser/src/main/java/seng302/Model/ResizableRaceCanvas.java +++ b/visualiser/src/main/java/seng302/Model/ResizableRaceCanvas.java @@ -31,6 +31,7 @@ public class ResizableRaceCanvas extends ResizableCanvas { private boolean annoAbbrev = true; private boolean annoSpeed = true; private boolean annoPath = true; + private boolean annoEstTime = true; private boolean annoTimeSinceLastMark = true; private List colours; private final List markers; @@ -177,7 +178,7 @@ public class ResizableRaceCanvas extends ResizableCanvas { * @param coordinate coordinate the text appears * @param timeSinceLastMark time since the last mark was passed */ - private void displayText(String name, String abbrev, double speed, GraphCoordinate coordinate, ZonedDateTime timeSinceLastMark) { + private void displayText(String name, String abbrev, double speed, GraphCoordinate coordinate, String estTime, ZonedDateTime timeSinceLastMark) { String text = ""; //Check name toggle value if (annoName){ @@ -191,10 +192,13 @@ public class ResizableRaceCanvas extends ResizableCanvas { if (annoSpeed){ text += String.format("%.2fkn ", speed); } + if (annoEstTime) { + text += estTime; + } //Check time since last mark toggle value if(annoTimeSinceLastMark){ Duration timeSince = Duration.between(timeSinceLastMark, raceClock.getTime()); - text += String.format("%d", timeSince.getSeconds()); + text += String.format(" %ds ", timeSince.getSeconds()); } //String text = String.format("%s, %2$.2fkn", name, speed); long xCoord = coordinate.getX() + 20; @@ -277,6 +281,10 @@ public class ResizableRaceCanvas extends ResizableCanvas { annoPath = !annoPath; } + public void toggleEstTime() { + annoEstTime = !annoEstTime; + } + /** * Toggle boat time display in annotation */ @@ -320,7 +328,7 @@ public class ResizableRaceCanvas extends ResizableCanvas { if (Duration.between(boat.getTimeSinceLastMark(), raceClock.getTime()).getSeconds() < 0) { boat.setTimeSinceLastMark(raceClock.getTime()); } - displayText(boat.toString(), boat.getAbbrev(), boat.getVelocity(), this.map.convertGPS(boat.getCurrentPosition()), boat.getTimeSinceLastMark()); + displayText(boat.toString(), boat.getAbbrev(), boat.getVelocity(), this.map.convertGPS(boat.getCurrentPosition()), boat.getFormattedEstTime(), boat.getTimeSinceLastMark()); //TODO this needs to be fixed. drawTrack(boat, boatColours.get(sourceID)); } diff --git a/visualiser/src/main/java/seng302/VisualiserInput.java b/visualiser/src/main/java/seng302/VisualiserInput.java index 4c3df9d9..1c552904 100644 --- a/visualiser/src/main/java/seng302/VisualiserInput.java +++ b/visualiser/src/main/java/seng302/VisualiserInput.java @@ -88,6 +88,10 @@ public class VisualiserInput implements Runnable { return boatLocationMap.get(sourceID); } + public BoatStatus getBoatStatusMessage(int sourceID) { + return boatStatusMap.get(sourceID); + } + /** * Calculates the time since last heartbeat, in milliseconds. * @return Time since last heartbeat, in milliseconds.. From 0a022e275e87240fe923b1af684d6c1dcd56cbca Mon Sep 17 00:00:00 2001 From: Erika Savell Date: Thu, 25 May 2017 02:04:42 +1200 Subject: [PATCH 04/11] Added checkbox for est time to next mark to gui #story[875] --- .../src/main/resources/scenes/race.fxml | 43 ++++++++++++------- 1 file changed, 28 insertions(+), 15 deletions(-) diff --git a/visualiser/src/main/resources/scenes/race.fxml b/visualiser/src/main/resources/scenes/race.fxml index 900b731d..4a4a9d21 100644 --- a/visualiser/src/main/resources/scenes/race.fxml +++ b/visualiser/src/main/resources/scenes/race.fxml @@ -1,15 +1,27 @@ - - - - - - + + + + + + + + + + + + + + + + + + - + @@ -24,7 +36,7 @@ - + @@ -33,13 +45,14 @@ - -