From 824a5ed3a2194ce5201a54e791b476c18ffec73b Mon Sep 17 00:00:00 2001 From: fjc40 Date: Tue, 23 May 2017 18:35:39 +1200 Subject: [PATCH 1/5] 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 2/5] 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 5d8380be8dfd0929ea2e2724dc2d1434932f0de8 Mon Sep 17 00:00:00 2001 From: fjc40 Date: Thu, 25 May 2017 12:35:57 +1200 Subject: [PATCH 3/5] Mock.Race: Boats now tack only every 15 seconds (see tackPeriod variable). Also doesn't attempt to calculate VMG (expensive operation) if it won't be used. --- mock/src/main/java/seng302/Model/Race.java | 31 +++++++++++++--------- 1 file changed, 19 insertions(+), 12 deletions(-) diff --git a/mock/src/main/java/seng302/Model/Race.java b/mock/src/main/java/seng302/Model/Race.java index d46840f5..ed6c5a4d 100644 --- a/mock/src/main/java/seng302/Model/Race.java +++ b/mock/src/main/java/seng302/Model/Race.java @@ -66,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 = 30; + private int scaleFactor = 15; /** * The race ID of the course. @@ -587,23 +587,30 @@ public class Race implements Runnable { boat.moveForwards(distanceTravelledMeters, updatePeriodMilliseconds * this.scaleFactor); - //Calculate the boat's bearing bounds, to ensure that it doesn't go out of the course. - Bearing[] bearingBounds = this.calculateBearingBounds(boat); + //Only get a new VMG if the boat will go outside the course, or X seconds have elapsed. + boolean willStayInsideCourse = this.checkBearingInsideCourse(boat.getBearing(), boat.getCurrentPosition()); + long tackPeriod = 15000; + if (!willStayInsideCourse || (boat.getTimeSinceTackChange() > tackPeriod)) { + //Calculate the 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); + //Calculate the new VMG. + VMG newVMG = this.calculateVMG(boat, bearingBounds); - //If the new vmg improves velocity, use it. - if (improvesVelocity(boat, newVMG)) { - boat.setVMG(newVMG); - } else { - //We also need to use the new VMG if our current bearing will take us out of the course. - if (!this.checkBearingInsideCourse(boat.getBearing(), boat.getCurrentPosition())) { + + //If the new vmg improves velocity, use it. + if (improvesVelocity(boat, newVMG)) { boat.setVMG(newVMG); - } + } else { + //We also need to use the new VMG if our current bearing will take us out of the course. + if (!willStayInsideCourse) { + boat.setVMG(newVMG); + } + + } } From f71e175f8053888767295e4f2be1601779e33597 Mon Sep 17 00:00:00 2001 From: fjc40 Date: Thu, 25 May 2017 13:53:27 +1200 Subject: [PATCH 4/5] Mock.Race: The race start time was current + prestart period + 1 second. It is now current + prestart + 1 minute. --- mock/src/main/java/seng302/Model/Race.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mock/src/main/java/seng302/Model/Race.java b/mock/src/main/java/seng302/Model/Race.java index 6f7e86fa..76698152 100644 --- a/mock/src/main/java/seng302/Model/Race.java +++ b/mock/src/main/java/seng302/Model/Race.java @@ -133,7 +133,7 @@ public class Race implements Runnable { this.raceId = raceData.getRaceId(); //The start time is current time + 4 minutes, scaled. prestart is 3 minutes, and we add another. - this.startTime = System.currentTimeMillis() + ((Constants.RacePreStartTime + (1 * 1000)) / this.scaleFactor); + this.startTime = System.currentTimeMillis() + ((Constants.RacePreStartTime + (1 * 60 * 1000)) / this.scaleFactor); this.setRaceStatusEnum(RaceStatusEnum.NOT_ACTIVE); this.raceType = RaceTypeEnum.FLEET_RACE; From e5ae2543f82754e31c6828b829a262d05e011380 Mon Sep 17 00:00:00 2001 From: Joseph Gardner Date: Thu, 25 May 2017 14:30:12 +1200 Subject: [PATCH 5/5] removed unneeded print statement and fixed incorrect comment (minor (2 lines)) --- mock/src/main/java/seng302/Model/Race.java | 1 - .../src/main/java/seng302/Networking/Messages/RaceStatus.java | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/mock/src/main/java/seng302/Model/Race.java b/mock/src/main/java/seng302/Model/Race.java index 775091c7..5b369dfb 100644 --- a/mock/src/main/java/seng302/Model/Race.java +++ b/mock/src/main/java/seng302/Model/Race.java @@ -382,7 +382,6 @@ public class Race implements Runnable { } else { //Otherwise, the race is over! - System.out.println("test"); raceFinished.start(); setRaceStatusEnum(RaceStatusEnum.FINISHED); this.stop(); diff --git a/network/src/main/java/seng302/Networking/Messages/RaceStatus.java b/network/src/main/java/seng302/Networking/Messages/RaceStatus.java index 599411ac..d04e06b8 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 / 65536.0; // 0x8000 / 360 + private static final double windDirectionScalar = 360.0 / 65536.0; // 0x10000 / 360 public RaceStatus(long currentTime, int raceID, int raceStatus, long expectedStartTime, int windDirection, int windSpeed, int raceType, List boatStatuses){ super(MessageType.RACESTATUS);