Merge branch 'story48' into Development
This commit is contained in:
@@ -55,26 +55,10 @@ public class MockRace extends Race {
|
|||||||
private int dnfChance = 0;
|
private int dnfChance = 0;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Used to generate random numbers when changing the wind direction.
|
* Object used to generate changes in wind speed/direction.
|
||||||
*/
|
*/
|
||||||
private int changeWind = 4;
|
private WindGenerator windGenerator;
|
||||||
|
|
||||||
/**
|
|
||||||
* The bearing the wind direction starts at.
|
|
||||||
*/
|
|
||||||
private static final Bearing windBaselineBearing = Bearing.fromDegrees(225);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The lower bearing angle that the wind may have.
|
|
||||||
*/
|
|
||||||
private static final Bearing windLowerBound = Bearing.fromDegrees(215);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The upper bearing angle that the wind may have.
|
|
||||||
*/
|
|
||||||
private static final Bearing windUpperBound = Bearing.fromDegrees(235);
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -99,10 +83,17 @@ public class MockRace extends Race {
|
|||||||
this.shrinkBoundary = GPSCoordinate.getShrinkBoundary(this.boundary);
|
this.shrinkBoundary = GPSCoordinate.getShrinkBoundary(this.boundary);
|
||||||
|
|
||||||
|
|
||||||
|
//Set up wind generator. It may be tidier to create this outside the race (with the values sourced from a data file maybe?) and pass it in.
|
||||||
|
this.windGenerator = new WindGenerator(
|
||||||
|
Bearing.fromDegrees(225),
|
||||||
|
Bearing.fromDegrees(215),
|
||||||
|
Bearing.fromDegrees(235),
|
||||||
|
12d,
|
||||||
|
8d,
|
||||||
|
16d );
|
||||||
|
|
||||||
this.windSpeed = 12;
|
//Wind.
|
||||||
this.windDirection = Bearing.fromDegrees(180);
|
this.setWind(windGenerator.generateBaselineWind());
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -288,8 +279,8 @@ public class MockRace extends Race {
|
|||||||
|
|
||||||
|
|
||||||
//Convert wind direction and speed to ints. //TODO this conversion should be done inside the racestatus class.
|
//Convert wind direction and speed to ints. //TODO this conversion should be done inside the racestatus class.
|
||||||
int windDirectionInt = AC35UnitConverter.encodeHeading(this.windDirection.degrees());
|
int windDirectionInt = AC35UnitConverter.encodeHeading(this.getWindDirection().degrees());
|
||||||
int windSpeedInt = (int) (this.windSpeed * Constants.KnotsToMMPerSecond);
|
int windSpeedInt = (int) (this.getWindSpeed() * Constants.KnotsToMMPerSecond);
|
||||||
|
|
||||||
//Create race status object, and send it.
|
//Create race status object, and send it.
|
||||||
RaceStatus raceStatus = new RaceStatus(
|
RaceStatus raceStatus = new RaceStatus(
|
||||||
@@ -576,7 +567,12 @@ public class MockRace extends Race {
|
|||||||
|
|
||||||
|
|
||||||
//Find the VMG inside these bounds.
|
//Find the VMG inside these bounds.
|
||||||
VMG bestVMG = boat.getPolars().calculateVMG(this.windDirection, this.windSpeed, boat.calculateBearingToNextMarker(), lowerAcceptableBound, upperAcceptableBound);
|
VMG bestVMG = boat.getPolars().calculateVMG(
|
||||||
|
this.getWindDirection(),
|
||||||
|
this.getWindSpeed(),
|
||||||
|
boat.calculateBearingToNextMarker(),
|
||||||
|
lowerAcceptableBound,
|
||||||
|
upperAcceptableBound);
|
||||||
|
|
||||||
|
|
||||||
return bestVMG;
|
return bestVMG;
|
||||||
@@ -884,7 +880,7 @@ public class MockRace extends Race {
|
|||||||
*/
|
*/
|
||||||
protected void initialiseWindDirection() {
|
protected void initialiseWindDirection() {
|
||||||
//Set the starting bearing.
|
//Set the starting bearing.
|
||||||
this.windDirection = Bearing.fromDegrees(MockRace.windBaselineBearing.degrees());
|
this.setWind(windGenerator.generateBaselineWind());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -893,27 +889,9 @@ public class MockRace extends Race {
|
|||||||
*/
|
*/
|
||||||
protected void changeWindDirection() {
|
protected void changeWindDirection() {
|
||||||
|
|
||||||
//Randomly add or remove 0.5 degrees.
|
Wind nextWind = windGenerator.generateNextWind(raceWind.getValue());
|
||||||
int r = new Random().nextInt(changeWind) + 1;
|
|
||||||
|
|
||||||
if (r == 1) {
|
setWind(nextWind);
|
||||||
//Add 0.5 degrees to the wind bearing.
|
|
||||||
this.windDirection.setDegrees(this.windDirection.degrees() + 0.5);
|
|
||||||
|
|
||||||
} else if (r == 2) {
|
|
||||||
//Minus 0.5 degrees from the wind bearing.
|
|
||||||
this.windDirection.setDegrees(this.windDirection.degrees() - 0.5);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
//Ensure that the wind is in the correct bounds.
|
|
||||||
if (this.windDirection.degrees() > MockRace.windUpperBound.degrees()) {
|
|
||||||
this.windDirection.setBearing(MockRace.windUpperBound);
|
|
||||||
|
|
||||||
} else if (this.windDirection.degrees() < MockRace.windLowerBound.degrees()) {
|
|
||||||
this.windDirection.setBearing(MockRace.windLowerBound);
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,249 @@
|
|||||||
|
package mock.model;
|
||||||
|
|
||||||
|
|
||||||
|
import shared.model.Bearing;
|
||||||
|
import shared.model.Wind;
|
||||||
|
|
||||||
|
import java.util.Random;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class generates Wind objects for use in a MockRace.
|
||||||
|
* Bounds on bearing and speed can be specified.
|
||||||
|
* Wind can be completely random, or random incremental change.
|
||||||
|
*/
|
||||||
|
public class WindGenerator {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The bearing the wind direction starts at.
|
||||||
|
*/
|
||||||
|
private Bearing windBaselineBearing;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The lower bearing angle that the wind may have.
|
||||||
|
*/
|
||||||
|
private Bearing windBearingLowerBound;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The upper bearing angle that the wind may have.
|
||||||
|
*/
|
||||||
|
private Bearing windBearingUpperBound;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The speed the wind starts at, in knots.
|
||||||
|
*/
|
||||||
|
private double windBaselineSpeed;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The lower speed that the wind may have, in knots.
|
||||||
|
*/
|
||||||
|
private double windSpeedLowerBound;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The upper speed that the wind may have, in knots.
|
||||||
|
*/
|
||||||
|
private double windSpeedUpperBound;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a wind generator, with a baseline, lower bound, and upper bound, for the wind speed and direction.
|
||||||
|
* @param windBaselineBearing Baseline wind direction.
|
||||||
|
* @param windBearingLowerBound Lower bound for wind direction.
|
||||||
|
* @param windBearingUpperBound Upper bound for wind direction.
|
||||||
|
* @param windBaselineSpeed Baseline wind speed, in knots.
|
||||||
|
* @param windSpeedLowerBound Lower bound for wind speed, in knots.
|
||||||
|
* @param windSpeedUpperBound Upper bound for wind speed, in knots.
|
||||||
|
*/
|
||||||
|
public WindGenerator(Bearing windBaselineBearing, Bearing windBearingLowerBound, Bearing windBearingUpperBound, double windBaselineSpeed, double windSpeedLowerBound, double windSpeedUpperBound) {
|
||||||
|
|
||||||
|
this.windBaselineBearing = windBaselineBearing;
|
||||||
|
this.windBearingLowerBound = windBearingLowerBound;
|
||||||
|
this.windBearingUpperBound = windBearingUpperBound;
|
||||||
|
this.windBaselineSpeed = windBaselineSpeed;
|
||||||
|
this.windSpeedLowerBound = windSpeedLowerBound;
|
||||||
|
this.windSpeedUpperBound = windSpeedUpperBound;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates a wind object using the baseline wind speed and bearing.
|
||||||
|
* @return Baseline wind object.
|
||||||
|
*/
|
||||||
|
public Wind generateBaselineWind() {
|
||||||
|
return new Wind(windBaselineBearing, windBaselineSpeed);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates a random Wind object, that is within the provided bounds.
|
||||||
|
* @return Generated wind object.
|
||||||
|
*/
|
||||||
|
public Wind generateRandomWind() {
|
||||||
|
|
||||||
|
double windSpeed = generateRandomWindSpeed();
|
||||||
|
Bearing windBearing = generateRandomWindBearing();
|
||||||
|
|
||||||
|
return new Wind(windBearing, windSpeed);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates a random wind speed within the specified bounds. In knots.
|
||||||
|
* @return Wind speed, in knots.
|
||||||
|
*/
|
||||||
|
private double generateRandomWindSpeed() {
|
||||||
|
|
||||||
|
double randomSpeedKnots = generateRandomValueInBounds(windSpeedLowerBound, windSpeedUpperBound);
|
||||||
|
|
||||||
|
return randomSpeedKnots;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates a random wind bearing within the specified bounds.
|
||||||
|
* @return Wind bearing.
|
||||||
|
*/
|
||||||
|
private Bearing generateRandomWindBearing() {
|
||||||
|
|
||||||
|
double randomBearingDegrees = generateRandomValueInBounds(windBearingLowerBound.degrees(), windBearingUpperBound.degrees());
|
||||||
|
|
||||||
|
return Bearing.fromDegrees(randomBearingDegrees);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates a random value within a specified interval.
|
||||||
|
* @param lowerBound The lower bound of the interval.
|
||||||
|
* @param upperBound The upper bound of the interval.
|
||||||
|
* @return A random value within the interval.
|
||||||
|
*/
|
||||||
|
private static double generateRandomValueInBounds(double lowerBound, double upperBound) {
|
||||||
|
|
||||||
|
float proportion = new Random().nextFloat();
|
||||||
|
|
||||||
|
double delta = upperBound - lowerBound;
|
||||||
|
|
||||||
|
double amount = delta * proportion;
|
||||||
|
|
||||||
|
double finalAmount = amount + lowerBound;
|
||||||
|
|
||||||
|
return finalAmount;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates a new value within an interval, given a start value, chance to change, and change amount.
|
||||||
|
* @param lowerBound Lower bound of interval.
|
||||||
|
* @param upperBound Upper bound of interval.
|
||||||
|
* @param currentValue The current value to change.
|
||||||
|
* @param changeAmount The amount to change by.
|
||||||
|
* @param chanceToChange The change to actually change the value.
|
||||||
|
* @return The new value.
|
||||||
|
*/
|
||||||
|
private static double generateNextValueInBounds(double lowerBound, double upperBound, double currentValue, double changeAmount, double chanceToChange) {
|
||||||
|
|
||||||
|
float chance = new Random().nextFloat();
|
||||||
|
|
||||||
|
|
||||||
|
if (chance <= chanceToChange) {
|
||||||
|
currentValue += changeAmount;
|
||||||
|
|
||||||
|
} else if (chance <= (2 * chanceToChange)) {
|
||||||
|
currentValue -= changeAmount;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
currentValue = clamp(lowerBound, upperBound, currentValue);
|
||||||
|
|
||||||
|
return currentValue;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates the next Wind object, that is within the provided bounds. This randomly increases or decreases the wind's speed and bearing.
|
||||||
|
* @param currentWind The current wind to change. This is not modified.
|
||||||
|
* @return Generated wind object.
|
||||||
|
*/
|
||||||
|
public Wind generateNextWind(Wind currentWind) {
|
||||||
|
|
||||||
|
double windSpeed = generateNextWindSpeed(currentWind.getWindSpeed());
|
||||||
|
Bearing windBearing = generateNextWindBearing(currentWind.getWindDirection());
|
||||||
|
|
||||||
|
return new Wind(windBearing, windSpeed);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates the next wind speed to use.
|
||||||
|
* @param windSpeed Current wind speed, in knots.
|
||||||
|
* @return Next wind speed, in knots.
|
||||||
|
*/
|
||||||
|
private double generateNextWindSpeed(double windSpeed) {
|
||||||
|
|
||||||
|
double chanceToChange = 0.2;
|
||||||
|
double changeAmount = 0.1;
|
||||||
|
|
||||||
|
double nextWindSpeed = generateNextValueInBounds(
|
||||||
|
windSpeedLowerBound,
|
||||||
|
windSpeedUpperBound,
|
||||||
|
windSpeed,
|
||||||
|
changeAmount,
|
||||||
|
chanceToChange);
|
||||||
|
|
||||||
|
return nextWindSpeed;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates the next wind speed to use.
|
||||||
|
* @param windBearing Current wind bearing.
|
||||||
|
* @return Next wind speed.
|
||||||
|
*/
|
||||||
|
private Bearing generateNextWindBearing(Bearing windBearing) {
|
||||||
|
|
||||||
|
double chanceToChange = 0.2;
|
||||||
|
double changeAmount = 0.5;
|
||||||
|
|
||||||
|
double nextWindBearingDegrees = generateNextValueInBounds(
|
||||||
|
windBearingLowerBound.degrees(),
|
||||||
|
windBearingUpperBound.degrees(),
|
||||||
|
windBearing.degrees(),
|
||||||
|
changeAmount,
|
||||||
|
chanceToChange);
|
||||||
|
|
||||||
|
return Bearing.fromDegrees(nextWindBearingDegrees);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clamps a value to be within an interval.
|
||||||
|
* @param lower Lower bound of the interval.
|
||||||
|
* @param upper Upper bound of the interval.
|
||||||
|
* @param value Value to clamp.
|
||||||
|
* @return The clamped value.
|
||||||
|
*/
|
||||||
|
private static double clamp(double lower, double upper, double value) {
|
||||||
|
|
||||||
|
if (value > upper) {
|
||||||
|
value = upper;
|
||||||
|
|
||||||
|
} else if (value < lower) {
|
||||||
|
value = lower;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,7 +1,9 @@
|
|||||||
package shared.model;
|
package shared.model;
|
||||||
|
|
||||||
import javafx.beans.property.IntegerProperty;
|
import javafx.beans.property.IntegerProperty;
|
||||||
|
import javafx.beans.property.Property;
|
||||||
import javafx.beans.property.SimpleIntegerProperty;
|
import javafx.beans.property.SimpleIntegerProperty;
|
||||||
|
import javafx.beans.property.SimpleObjectProperty;
|
||||||
import network.Messages.Enums.RaceStatusEnum;
|
import network.Messages.Enums.RaceStatusEnum;
|
||||||
import network.Messages.Enums.RaceTypeEnum;
|
import network.Messages.Enums.RaceTypeEnum;
|
||||||
import network.Messages.LatestMessages;
|
import network.Messages.LatestMessages;
|
||||||
@@ -98,15 +100,9 @@ public abstract class Race implements Runnable {
|
|||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The current wind direction bearing.
|
* The race's wind.
|
||||||
*/
|
*/
|
||||||
protected Bearing windDirection;
|
protected Property<Wind> raceWind = new SimpleObjectProperty<>();
|
||||||
|
|
||||||
/**
|
|
||||||
* Wind speed (knots).
|
|
||||||
* Convert this to millimeters per second before passing to RaceStatus.
|
|
||||||
*/
|
|
||||||
protected double windSpeed;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -169,11 +165,8 @@ public abstract class Race implements Runnable {
|
|||||||
//Race type.
|
//Race type.
|
||||||
this.raceType = raceDataSource.getRaceType();
|
this.raceType = raceDataSource.getRaceType();
|
||||||
|
|
||||||
//Wind speed.
|
//Wind.
|
||||||
this.windSpeed = 0;
|
this.setWind(Bearing.fromDegrees(0), 0);
|
||||||
//Wind direction.
|
|
||||||
this.windDirection = Bearing.fromDegrees(0);
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -254,12 +247,32 @@ public abstract class Race implements Runnable {
|
|||||||
return regattaName;
|
return regattaName;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the race to have a specified wind bearing and speed.
|
||||||
|
* @param windBearing New wind bearing.
|
||||||
|
* @param windSpeedKnots New wind speed, in knots.
|
||||||
|
*/
|
||||||
|
protected void setWind(Bearing windBearing, double windSpeedKnots) {
|
||||||
|
Wind wind = new Wind(windBearing, windSpeedKnots);
|
||||||
|
setWind(wind);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the race to have a specified wind (bearing and speed).
|
||||||
|
* @param wind New wind.
|
||||||
|
*/
|
||||||
|
protected void setWind(Wind wind) {
|
||||||
|
this.raceWind.setValue(wind);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the wind bearing.
|
* Returns the wind bearing.
|
||||||
* @return The wind bearing.
|
* @return The wind bearing.
|
||||||
*/
|
*/
|
||||||
public Bearing getWindDirection() {
|
public Bearing getWindDirection() {
|
||||||
return windDirection;
|
return raceWind.getValue().getWindDirection();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -268,7 +281,15 @@ public abstract class Race implements Runnable {
|
|||||||
* @return The wind speed.
|
* @return The wind speed.
|
||||||
*/
|
*/
|
||||||
public double getWindSpeed() {
|
public double getWindSpeed() {
|
||||||
return windSpeed;
|
return raceWind.getValue().getWindSpeed();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the race's wind.
|
||||||
|
* @return The race's wind.
|
||||||
|
*/
|
||||||
|
public Property<Wind> windProperty() {
|
||||||
|
return raceWind;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -0,0 +1,51 @@
|
|||||||
|
package shared.model;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class encapsulates the wind during a race.
|
||||||
|
* It has speed and a bearing.
|
||||||
|
* This is intended to be immutable.
|
||||||
|
*/
|
||||||
|
public class Wind {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The current wind direction bearing.
|
||||||
|
*/
|
||||||
|
private Bearing windDirection;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wind speed (knots).
|
||||||
|
* Convert this to millimeters per second before passing to RaceStatus.
|
||||||
|
*/
|
||||||
|
private double windSpeed;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a new wind object, with a given direction and speed, in knots.
|
||||||
|
* @param windDirection The direction of the wind.
|
||||||
|
* @param windSpeed The speed of the wind, in knots.
|
||||||
|
*/
|
||||||
|
public Wind(Bearing windDirection, double windSpeed) {
|
||||||
|
this.windDirection = windDirection;
|
||||||
|
this.windSpeed = windSpeed;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the race wind's bearing.
|
||||||
|
* @return The race wind's bearing.
|
||||||
|
*/
|
||||||
|
public Bearing getWindDirection() {
|
||||||
|
return windDirection;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the race wind's speed, in knots.
|
||||||
|
* @return The race wind's speed, in knots.
|
||||||
|
*/
|
||||||
|
public double getWindSpeed() {
|
||||||
|
return windSpeed;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,152 @@
|
|||||||
|
package visualiser.Controllers;
|
||||||
|
|
||||||
|
|
||||||
|
import javafx.application.Platform;
|
||||||
|
import javafx.beans.property.Property;
|
||||||
|
import javafx.fxml.FXML;
|
||||||
|
import javafx.scene.Node;
|
||||||
|
import javafx.scene.control.Label;
|
||||||
|
import javafx.scene.image.ImageView;
|
||||||
|
import javafx.scene.layout.Pane;
|
||||||
|
import javafx.scene.layout.StackPane;
|
||||||
|
import javafx.scene.shape.Circle;
|
||||||
|
import shared.model.Bearing;
|
||||||
|
import shared.model.Wind;
|
||||||
|
import visualiser.model.VisualiserRace;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Controller for the arrow.fxml view.
|
||||||
|
*/
|
||||||
|
public class ArrowController {
|
||||||
|
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private Pane compass;
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private StackPane arrowStackPane;
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private ImageView arrowImage;
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private Circle circle;
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private Label northLabel;
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private Label windLabel;
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private Label speedLabel;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is the property our arrow control binds to.
|
||||||
|
*/
|
||||||
|
private Property<Wind> wind;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor.
|
||||||
|
*/
|
||||||
|
public ArrowController() {
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets which wind property the arrow control should bind to.
|
||||||
|
* @param wind The wind property to bind to.
|
||||||
|
*/
|
||||||
|
public void setWindProperty(Property<Wind> wind) {
|
||||||
|
this.wind = wind;
|
||||||
|
|
||||||
|
wind.addListener((observable, oldValue, newValue) -> {
|
||||||
|
if (newValue != null) {
|
||||||
|
Platform.runLater(() -> updateWind(newValue));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the control to use the new wind value.
|
||||||
|
* This updates the arrow direction (due to bearing), arrow length (due to speed), and label (due to speed).
|
||||||
|
* @param wind The wind value to use.
|
||||||
|
*/
|
||||||
|
private void updateWind(Wind wind) {
|
||||||
|
updateWindBearing(wind.getWindDirection());
|
||||||
|
updateWindSpeed(wind.getWindSpeed());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the control to account for the new wind speed.
|
||||||
|
* This changes the length (height) of the wind arrow, and updates the speed label.
|
||||||
|
* @param speedKnots The new wind speed, in knots.
|
||||||
|
*/
|
||||||
|
private void updateWindSpeed(double speedKnots) {
|
||||||
|
updateWindArrowLength(speedKnots);
|
||||||
|
updateWindSpeedLabel(speedKnots);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the length of the wind arrow according to the specified wind speed.
|
||||||
|
* @param speedKnots Wind speed, in knots.
|
||||||
|
*/
|
||||||
|
private void updateWindArrowLength(double speedKnots) {
|
||||||
|
|
||||||
|
//At 2 knots, the arrow reaches its minimum height, and at 30 knots it reaches its maximum height.
|
||||||
|
double minKnots = 2;
|
||||||
|
double maxKnots = 30;
|
||||||
|
double deltaKnots = maxKnots - minKnots;
|
||||||
|
|
||||||
|
double minHeight = 25;
|
||||||
|
double maxHeight = 75;
|
||||||
|
double deltaHeight = maxHeight - minHeight;
|
||||||
|
|
||||||
|
//Clamp speed.
|
||||||
|
if (speedKnots > maxKnots) {
|
||||||
|
speedKnots = maxKnots;
|
||||||
|
} else if (speedKnots < minKnots) {
|
||||||
|
speedKnots = minKnots;
|
||||||
|
}
|
||||||
|
|
||||||
|
//How far between the knots bounds is the current speed?
|
||||||
|
double currentDeltaKnots = speedKnots - minKnots;
|
||||||
|
double currentKnotsScalar = currentDeltaKnots / deltaKnots;
|
||||||
|
|
||||||
|
//Thus, how far between the pixel height bounds should the arrow height be?
|
||||||
|
double newHeight = minHeight + (currentKnotsScalar * deltaHeight);
|
||||||
|
|
||||||
|
arrowImage.setFitHeight(newHeight);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the wind speed label according to the specified wind speed.
|
||||||
|
* @param speedKnots Wind speed, in knots.
|
||||||
|
*/
|
||||||
|
private void updateWindSpeedLabel(double speedKnots) {
|
||||||
|
speedLabel.setText(String.format("%.1fkn", speedKnots));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the control to account for a new wind bearing.
|
||||||
|
* This rotates the arrow according to the bearing.
|
||||||
|
* @param bearing The bearing to use to rotate arrow.
|
||||||
|
*/
|
||||||
|
private void updateWindBearing(Bearing bearing) {
|
||||||
|
|
||||||
|
//We need to display wind-from, so add 180 degrees.
|
||||||
|
Bearing fromBearing = Bearing.fromDegrees(bearing.degrees() + 180d);
|
||||||
|
|
||||||
|
//Rotate the wind arrow.
|
||||||
|
arrowStackPane.setRotate(fromBearing.degrees());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -5,7 +5,6 @@ import javafx.animation.AnimationTimer;
|
|||||||
import javafx.application.Platform;
|
import javafx.application.Platform;
|
||||||
import javafx.collections.FXCollections;
|
import javafx.collections.FXCollections;
|
||||||
import javafx.collections.ObservableList;
|
import javafx.collections.ObservableList;
|
||||||
import javafx.event.EventHandler;
|
|
||||||
import javafx.fxml.FXML;
|
import javafx.fxml.FXML;
|
||||||
import javafx.scene.chart.LineChart;
|
import javafx.scene.chart.LineChart;
|
||||||
import javafx.scene.control.*;
|
import javafx.scene.control.*;
|
||||||
@@ -24,10 +23,8 @@ import visualiser.gameController.Keys.ControlKey;
|
|||||||
import visualiser.gameController.Keys.KeyFactory;
|
import visualiser.gameController.Keys.KeyFactory;
|
||||||
import visualiser.model.*;
|
import visualiser.model.*;
|
||||||
|
|
||||||
import java.awt.*;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
import java.text.DecimalFormat;
|
|
||||||
import java.util.ResourceBundle;
|
import java.util.ResourceBundle;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -60,6 +57,11 @@ public class RaceController extends Controller {
|
|||||||
*/
|
*/
|
||||||
private Sparkline sparkline;
|
private Sparkline sparkline;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The arrow controller.
|
||||||
|
*/
|
||||||
|
@FXML private ArrowController arrowController;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Service for sending keystrokes to server
|
* Service for sending keystrokes to server
|
||||||
*/
|
*/
|
||||||
@@ -67,8 +69,18 @@ public class RaceController extends Controller {
|
|||||||
|
|
||||||
|
|
||||||
@FXML private GridPane canvasBase;
|
@FXML private GridPane canvasBase;
|
||||||
@FXML private Pane arrow;
|
|
||||||
|
|
||||||
@FXML private SplitPane race;
|
@FXML private SplitPane race;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is the root node of the arrow control.
|
||||||
|
*/
|
||||||
|
@FXML private Pane arrow;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is the pane we place the actual arrow control inside of.
|
||||||
|
*/
|
||||||
@FXML private StackPane arrowPane;
|
@FXML private StackPane arrowPane;
|
||||||
@FXML private Label timer;
|
@FXML private Label timer;
|
||||||
@FXML private Label FPS;
|
@FXML private Label FPS;
|
||||||
@@ -118,15 +130,15 @@ public class RaceController extends Controller {
|
|||||||
//Fps display.
|
//Fps display.
|
||||||
initialiseFps(this.visualiserRace);
|
initialiseFps(this.visualiserRace);
|
||||||
|
|
||||||
//Need to add the included arrow pane to the arrowPane container.
|
|
||||||
initialiseArrow();
|
|
||||||
|
|
||||||
//Information table.
|
//Information table.
|
||||||
initialiseInfoTable(this.visualiserRace);
|
initialiseInfoTable(this.visualiserRace);
|
||||||
|
|
||||||
//Sparkline.
|
//Sparkline.
|
||||||
initialiseSparkline(this.visualiserRace);
|
initialiseSparkline(this.visualiserRace);
|
||||||
|
|
||||||
|
//Arrow.
|
||||||
|
initialiseArrow(this.visualiserRace);
|
||||||
|
|
||||||
//Race canvas.
|
//Race canvas.
|
||||||
initialiseRaceCanvas(this.visualiserRace);
|
initialiseRaceCanvas(this.visualiserRace);
|
||||||
|
|
||||||
@@ -294,7 +306,7 @@ public class RaceController extends Controller {
|
|||||||
private void initialiseRaceCanvas(VisualiserRace race) {
|
private void initialiseRaceCanvas(VisualiserRace race) {
|
||||||
|
|
||||||
//Create canvas.
|
//Create canvas.
|
||||||
raceCanvas = new ResizableRaceCanvas(race, arrow.getChildren().get(0));
|
raceCanvas = new ResizableRaceCanvas(race);
|
||||||
|
|
||||||
//Set properties.
|
//Set properties.
|
||||||
raceCanvas.setMouseTransparent(true);
|
raceCanvas.setMouseTransparent(true);
|
||||||
@@ -367,10 +379,11 @@ public class RaceController extends Controller {
|
|||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds the included arrow pane (see arrow.fxml) to the arrowPane (see race.fxml).
|
* Initialises the arrow controller with data from the race to observe.
|
||||||
|
* @param race The race to observe.
|
||||||
*/
|
*/
|
||||||
private void initialiseArrow() {
|
private void initialiseArrow(VisualiserRace race) {
|
||||||
arrowPane.getChildren().add(arrow);
|
arrowController.setWindProperty(race.windProperty());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -54,22 +54,15 @@ public class ResizableRaceCanvas extends ResizableCanvas {
|
|||||||
private boolean annoTimeSinceLastMark = true;
|
private boolean annoTimeSinceLastMark = true;
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The wind arrow node.
|
|
||||||
*/
|
|
||||||
private Node arrow;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructs a {@link ResizableRaceCanvas} using a given {@link VisualiserRace}.
|
* Constructs a {@link ResizableRaceCanvas} using a given {@link VisualiserRace}.
|
||||||
* @param visualiserRace The race that data is read from in order to be drawn.
|
* @param visualiserRace The race that data is read from in order to be drawn.
|
||||||
* @param arrow The wind arrow's node.
|
|
||||||
*/
|
*/
|
||||||
public ResizableRaceCanvas(VisualiserRace visualiserRace, Node arrow) {
|
public ResizableRaceCanvas(VisualiserRace visualiserRace) {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
this.visualiserRace = visualiserRace;
|
this.visualiserRace = visualiserRace;
|
||||||
this.arrow = arrow;
|
|
||||||
|
|
||||||
RaceDataSource raceData = visualiserRace.getRaceDataSource();
|
RaceDataSource raceData = visualiserRace.getRaceDataSource();
|
||||||
|
|
||||||
@@ -375,32 +368,6 @@ public class ResizableRaceCanvas extends ResizableCanvas {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Displays an arrow representing wind direction on the Canvas.
|
|
||||||
* This function accepts a wind-to bearing, but displays a wind-from bearing.
|
|
||||||
*
|
|
||||||
* @param angle Angle that the arrow is to be facing in degrees 0 degrees = North (Up).
|
|
||||||
* @see GraphCoordinate
|
|
||||||
*/
|
|
||||||
private void displayWindArrow(double angle) {
|
|
||||||
|
|
||||||
//We need to display wind-from, so add 180 degrees.
|
|
||||||
angle += 180d;
|
|
||||||
|
|
||||||
//Get it within [0, 360).
|
|
||||||
while (angle >= 360d) {
|
|
||||||
angle -= 360d;
|
|
||||||
}
|
|
||||||
|
|
||||||
//Rotate the wind arrow.
|
|
||||||
if (arrow != null && arrow.getRotate() != angle) {
|
|
||||||
arrow.setRotate(angle);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Draws all of the {@link Mark}s on the canvas.
|
* Draws all of the {@link Mark}s on the canvas.
|
||||||
*/
|
*/
|
||||||
@@ -511,9 +478,6 @@ public class ResizableRaceCanvas extends ResizableCanvas {
|
|||||||
//Marks.
|
//Marks.
|
||||||
drawMarks();
|
drawMarks();
|
||||||
|
|
||||||
//Wind arrow. This rotates the wind arrow node.
|
|
||||||
displayWindArrow(this.visualiserRace.getWindDirection().degrees());
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -311,11 +311,10 @@ public class VisualiserRace extends Race {
|
|||||||
//Race status enum.
|
//Race status enum.
|
||||||
this.raceStatusEnum = RaceStatusEnum.fromByte(raceStatus.getRaceStatus());
|
this.raceStatusEnum = RaceStatusEnum.fromByte(raceStatus.getRaceStatus());
|
||||||
|
|
||||||
//Wind bearing.
|
//Wind.
|
||||||
this.windDirection.setDegrees(raceStatus.getScaledWindDirection());
|
this.setWind(
|
||||||
|
Bearing.fromDegrees(raceStatus.getScaledWindDirection()),
|
||||||
//Wind speed.
|
raceStatus.getWindSpeedKnots() );
|
||||||
this.windSpeed = raceStatus.getWindSpeedKnots();
|
|
||||||
|
|
||||||
//Current race time.
|
//Current race time.
|
||||||
this.raceClock.setUTCTime(raceStatus.getCurrentTime());
|
this.raceClock.setUTCTime(raceStatus.getCurrentTime());
|
||||||
|
|||||||
@@ -1,34 +1,58 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
|
||||||
<?import javafx.scene.paint.*?>
|
<?import javafx.geometry.Insets?>
|
||||||
<?import javafx.scene.text.*?>
|
<?import javafx.scene.control.Label?>
|
||||||
<?import javafx.scene.control.*?>
|
<?import javafx.scene.image.Image?>
|
||||||
<?import javafx.scene.shape.*?>
|
<?import javafx.scene.image.ImageView?>
|
||||||
<?import javafx.scene.image.*?>
|
<?import javafx.scene.layout.ColumnConstraints?>
|
||||||
<?import java.lang.*?>
|
<?import javafx.scene.layout.GridPane?>
|
||||||
<?import javafx.scene.layout.*?>
|
<?import javafx.scene.layout.Pane?>
|
||||||
|
<?import javafx.scene.layout.RowConstraints?>
|
||||||
|
<?import javafx.scene.layout.StackPane?>
|
||||||
|
<?import javafx.scene.shape.Circle?>
|
||||||
|
<?import javafx.scene.text.Font?>
|
||||||
|
|
||||||
<Pane fx:id="compass" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="125.0" prefWidth="125.0" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1">
|
|
||||||
|
<GridPane fx:id="arrowGridPane" maxHeight="-Infinity" maxWidth="-Infinity" xmlns="http://javafx.com/javafx/8.0.111" xmlns:fx="http://javafx.com/fxml/1" fx:controller="visualiser.Controllers.ArrowController">
|
||||||
|
<columnConstraints>
|
||||||
|
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" />
|
||||||
|
</columnConstraints>
|
||||||
|
<rowConstraints>
|
||||||
|
<RowConstraints minHeight="10.0" vgrow="SOMETIMES" />
|
||||||
|
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
|
||||||
|
</rowConstraints>
|
||||||
<children>
|
<children>
|
||||||
<StackPane fx:id="arrow" prefHeight="125.0" prefWidth="125.0">
|
<Pane fx:id="compass" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="125.0" prefWidth="125.0">
|
||||||
<children>
|
<children>
|
||||||
<ImageView fitHeight="75.0" fitWidth="75.0">
|
<StackPane fx:id="arrowStackPane" prefHeight="125.0" prefWidth="125.0">
|
||||||
<image>
|
<children>
|
||||||
<Image url="@../images/arrow.png" />
|
<ImageView fx:id="arrowImage" fitHeight="75.0" fitWidth="75.0">
|
||||||
</image>
|
<image>
|
||||||
</ImageView>
|
<Image url="@../images/arrow.png" />
|
||||||
|
</image>
|
||||||
|
</ImageView>
|
||||||
|
</children>
|
||||||
|
</StackPane>
|
||||||
|
<Circle fx:id="circle" fill="#1f93ff00" layoutX="63.0" layoutY="63.0" radius="60.0" stroke="BLACK" strokeType="INSIDE" strokeWidth="3.0" />
|
||||||
|
<Label fx:id="northLabel" layoutX="55.0" layoutY="1.0" text="N">
|
||||||
|
<font>
|
||||||
|
<Font name="System Bold" size="18.0" />
|
||||||
|
</font>
|
||||||
|
</Label>
|
||||||
|
<Label fx:id="windLabel" layoutX="42.0" layoutY="95.0" text="Wind">
|
||||||
|
<font>
|
||||||
|
<Font name="System Bold" size="16.0" />
|
||||||
|
</font>
|
||||||
|
</Label>
|
||||||
</children>
|
</children>
|
||||||
</StackPane>
|
</Pane>
|
||||||
<Circle fill="#1f93ff00" layoutX="63.0" layoutY="63.0" radius="60.0" stroke="BLACK" strokeType="INSIDE" strokeWidth="3.0" />
|
<Label fx:id="speedLabel" text="SPEED" GridPane.halignment="CENTER" GridPane.hgrow="NEVER" GridPane.rowIndex="1">
|
||||||
<Label layoutX="55.0" layoutY="1.0" text="N">
|
|
||||||
<font>
|
|
||||||
<Font name="System Bold" size="18.0" />
|
|
||||||
</font>
|
|
||||||
</Label>
|
|
||||||
<Label layoutX="42.0" layoutY="99.0" text="Wind">
|
|
||||||
<font>
|
<font>
|
||||||
<Font name="System Bold" size="16.0" />
|
<Font name="System Bold" size="16.0" />
|
||||||
</font>
|
</font>
|
||||||
|
<GridPane.margin>
|
||||||
|
<Insets />
|
||||||
|
</GridPane.margin>
|
||||||
</Label>
|
</Label>
|
||||||
</children>
|
</children>
|
||||||
</Pane>
|
</GridPane>
|
||||||
|
|||||||
@@ -16,7 +16,6 @@
|
|||||||
<RowConstraints minHeight="10.0" vgrow="SOMETIMES" />
|
<RowConstraints minHeight="10.0" vgrow="SOMETIMES" />
|
||||||
</rowConstraints>
|
</rowConstraints>
|
||||||
<children>
|
<children>
|
||||||
<fx:include fx:id="arrow" source="arrow.fxml" />
|
|
||||||
<Pane prefHeight="200.0" prefWidth="400.0" GridPane.halignment="LEFT" GridPane.valignment="TOP">
|
<Pane prefHeight="200.0" prefWidth="400.0" GridPane.halignment="LEFT" GridPane.valignment="TOP">
|
||||||
<children>
|
<children>
|
||||||
<Accordion>
|
<Accordion>
|
||||||
@@ -76,7 +75,11 @@
|
|||||||
<Font name="System Bold" size="15.0" />
|
<Font name="System Bold" size="15.0" />
|
||||||
</font>
|
</font>
|
||||||
</Label>
|
</Label>
|
||||||
<StackPane fx:id="arrowPane" alignment="TOP_RIGHT" mouseTransparent="true" prefHeight="150.0" prefWidth="150.0" snapToPixel="false" />
|
<StackPane fx:id="arrowPane" alignment="TOP_RIGHT" mouseTransparent="true" prefHeight="150.0" prefWidth="150.0" snapToPixel="false">
|
||||||
|
<children>
|
||||||
|
<fx:include fx:id="arrow" source="arrow.fxml" />
|
||||||
|
</children>
|
||||||
|
</StackPane>
|
||||||
</children>
|
</children>
|
||||||
</GridPane>
|
</GridPane>
|
||||||
<AnchorPane layoutX="450.0" minHeight="0.0" minWidth="0.0" prefHeight="160.0" prefWidth="200.0" GridPane.columnIndex="1">
|
<AnchorPane layoutX="450.0" minHeight="0.0" minWidth="0.0" prefHeight="160.0" prefWidth="200.0" GridPane.columnIndex="1">
|
||||||
|
|||||||
Reference in New Issue
Block a user