diff --git a/mock/src/main/java/seng302/Model/Race.java b/mock/src/main/java/seng302/Model/Race.java index 92b51144..fa9223fe 100644 --- a/mock/src/main/java/seng302/Model/Race.java +++ b/mock/src/main/java/seng302/Model/Race.java @@ -64,7 +64,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 = 25; + private int scaleFactor = 5; /** * The race ID of the course. @@ -483,24 +483,43 @@ public class Race implements Runnable { /** * Calculates a boat's VMG. * @param boat The boat to calculate VMG for. + * @param bearingBounds An array containing the lower and upper acceptable bearing bounds to keep the boat in the course. * @return VMG for the specified boat. */ - private VMG calculateVMG(Boat boat) { + 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 = Math.max(boat.getBearing().degrees() - turnAngle, 0); - double bound2Degrees = Math.min(boat.getBearing().degrees() + turnAngle, 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()); + + 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); + } @@ -557,9 +576,11 @@ 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); //Calculate the new VMG. - VMG newVMG = this.calculateVMG(boat); + VMG newVMG = this.calculateVMG(boat, bearingBounds); //If the new vmg improves velocity, use it. @@ -568,12 +589,8 @@ public class Race implements Runnable { } - //Ensure that the boat doesn't leave the course bounds. - this.forceBoatBearingInBounds(boat); - - //Check the boats position (update leg and stuff). - checkPosition(boat, totalTimeElapsed); + this.checkPosition(boat, totalTimeElapsed); } @@ -581,22 +598,81 @@ public class Race implements Runnable { /** - * Checks if the boat's current bearing would put it out of course bounds, and adjusts it if it does. + * Calculates the upper and lower bounds that the boat may have in order to not go outside of the course. * @param boat The boat to check. + * @return An array of bearings. The first is the lower bound, the second is the upper bound. + */ + private Bearing[] calculateBearingBounds(Boat boat) { + + Bearing[] bearings = new Bearing[2]; + + Bearing lowerBearing = Bearing.fromDegrees(0); + Bearing upperBearing = Bearing.fromDegrees(359.999); + + + boolean foundAnyBadBearing = false; + boolean foundLowerBearing = false; + + //Check all bearings between [0, 360). + for (double angle = 0; angle < 360; angle += 1) { + + //Create bearing from angle. + Bearing bearing = Bearing.fromDegrees(angle); + + //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; + } + + //The upper bearing will be the last acceptable bearing before finding any unacceptable bearing. + if (!foundAnyBadBearing) { + upperBearing = bearing; + } + + + } else { + + foundAnyBadBearing = true; + + } + + } + + + bearings[0] = lowerBearing; + bearings[1] = upperBearing; + + return bearings; + } + + + + /** + * 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. */ - private void forceBoatBearingInBounds(Boat boat) { + private boolean checkBearingInsideCourse(Bearing bearing, GPSCoordinate position) { - //Get the boat's azimuth. - Azimuth azimuth = Azimuth.fromBearing(boat.getBearing()); + //Get azimuth from bearing. + Azimuth azimuth = Azimuth.fromBearing(bearing); - //Tests to see if a point in front of the boat is out of bounds, if so mirror heading in the wind. + + //Tests to see if a point in front of the boat is out of bounds. double epsilonMeters = 100d; - GPSCoordinate testCoord = GPSCoordinate.calculateNewPosition(boat.getCurrentPosition(), epsilonMeters, azimuth); + GPSCoordinate testCoord = GPSCoordinate.calculateNewPosition(position, epsilonMeters, azimuth); //If it isn't inside the boundary, calculate new bearing. - if (!GPSCoordinate.isInsideBoundary(testCoord, this.boundary)) { - Bearing tempBearing = Bearing.fromDegrees(boat.getBearing().degrees() - this.windDirection.degrees() + 90); - boat.setBearing(tempBearing); + if (GPSCoordinate.isInsideBoundary(testCoord, this.boundary)) { + return true; + } else { + return false; } } diff --git a/visualiser/src/main/java/seng302/Controllers/StartController.java b/visualiser/src/main/java/seng302/Controllers/StartController.java index 464599a6..3004ab6c 100644 --- a/visualiser/src/main/java/seng302/Controllers/StartController.java +++ b/visualiser/src/main/java/seng302/Controllers/StartController.java @@ -54,6 +54,9 @@ public class StartController extends Controller implements Observer { ///Tracks whether the race has been started (that is, has startRaceNoScaling() be called). private boolean hasRaceStarted = false; + //Tracks whether or not a clock has been created and setup, which occurs after receiving enough information. + private boolean hasCreatedClock = false; + /** * Begins the race with a scale factor of 1 */ @@ -106,7 +109,14 @@ public class StartController extends Controller implements Observer { private void setRaceClock() { raceClock = new RaceClock(raceData.getZonedDateTime()); - timeZoneTime.textProperty().bind(raceClock.timeStringProperty()); + + raceClock.timeStringProperty().addListener((observable, oldValue, newValue) -> { + Platform.runLater(() -> { + timeZoneTime.setText(newValue); + }); + }); +//TEMP REMOVE + //timeZoneTime.textProperty().bind(raceClock.timeStringProperty()); raceClock.run(); } @@ -116,7 +126,12 @@ public class StartController extends Controller implements Observer { long utcTime = visualiserInput.getRaceStatus().getExpectedStartTime(); raceClock.setStartingTime(raceClock.getLocalTime(utcTime)); raceStartLabel.setText(DateTimeFormatter.ofPattern(dateFormat).format(raceClock.getStartingTime())); - timer.textProperty().bind(raceClock.durationProperty()); + + raceClock.durationProperty().addListener((observable, oldValue, newValue) -> { + Platform.runLater(() -> { + timer.setText(newValue); + }); + }); }); } @@ -138,12 +153,15 @@ public class StartController extends Controller implements Observer { } if (streamedCourse.hasReadCourse()) { Platform.runLater(() -> { - setRaceClock(); - if(visualiserInput.getRaceStatus() == null){ - return;//TEMP BUG FIX if the race isn't sending race status messages (our mock currently doesn't), then it would block the javafx thread with the previous while loop. - }// TODO - replace with observer on VisualiserInput - setStartingTime(); - setCurrentTime(); + if (!this.hasCreatedClock) { + this.hasCreatedClock = true; + setRaceClock(); + if (visualiserInput.getRaceStatus() == null) { + return;//TEMP BUG FIX if the race isn't sending race status messages (our mock currently doesn't), then it would block the javafx thread with the previous while loop. + }// TODO - replace with observer on VisualiserInput + setStartingTime(); + setCurrentTime(); + } }); } diff --git a/visualiser/src/main/java/seng302/Model/RaceClock.java b/visualiser/src/main/java/seng302/Model/RaceClock.java index f17b9e12..1e36224d 100644 --- a/visualiser/src/main/java/seng302/Model/RaceClock.java +++ b/visualiser/src/main/java/seng302/Model/RaceClock.java @@ -59,6 +59,8 @@ public class RaceClock implements Runnable { this.time = time; this.timeString.set(DateTimeFormatter.ofPattern("HH:mm:ss dd/MM/YYYY Z").format(time)); this.lastTime = System.currentTimeMillis(); + + if(startingTime != null) { long seconds = Duration.between(startingTime.toLocalDateTime(), time.toLocalDateTime()).getSeconds(); if(seconds < 0) @@ -66,6 +68,7 @@ public class RaceClock implements Runnable { else duration.set(String.format("Time: %02d:%02d:%02d", seconds/3600, (seconds%3600)/60, seconds%60)); } + } /**