Merge remote-tracking branch 'origin/master' into story_61

main
fjc40 8 years ago
commit 3ec87582d3

@ -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());
} }

@ -12,7 +12,6 @@ import shared.model.GPSCoordinate;
import shared.model.Mark; import shared.model.Mark;
import shared.model.RaceClock; import shared.model.RaceClock;
import java.time.Duration;
import java.util.List; import java.util.List;
/** /**
@ -39,12 +38,6 @@ public class ResizableRaceCanvas extends ResizableCanvas {
*/ */
private VisualiserRace visualiserRace; private VisualiserRace visualiserRace;
/**
* The background of the race.
* We render the background whenever the race boundary changes, or the screen size changes.
*/
private Image background;
private boolean annoName = true; private boolean annoName = true;
private boolean annoAbbrev = true; private boolean annoAbbrev = true;
@ -54,22 +47,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 +361,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.
*/ */
@ -440,13 +400,9 @@ public class ResizableRaceCanvas extends ResizableCanvas {
this.map.setWidth((int) getWidth()); this.map.setWidth((int) getWidth());
this.map.setHeight((int) getHeight()); this.map.setHeight((int) getHeight());
//Redraw the boundary.
redrawBoundaryImage();
//Draw the race. //Draw the race.
drawRace(); drawRace();
} }
@ -459,15 +415,13 @@ public class ResizableRaceCanvas extends ResizableCanvas {
/** /**
* Draws the race boundary, and saves the image to {@link #background}. * Draws the race boundary.
* You should call {@link #clear()} before calling this.
*/ */
private void redrawBoundaryImage() { private void drawBoundary() {
//Prepare to draw. //Prepare to draw.
gc.setLineWidth(1); gc.setLineWidth(1);
gc.setFill(Color.AQUA); gc.setFill(Color.AQUA);
gc.drawImage(new Image(getClass().getClassLoader().getResourceAsStream("images/WaterBackground.png")), 0, 0);
//Calculate the screen coordinates of the boundary. //Calculate the screen coordinates of the boundary.
@ -487,9 +441,6 @@ public class ResizableRaceCanvas extends ResizableCanvas {
//Draw the boundary. //Draw the boundary.
gc.fillPolygon(xpoints, ypoints, xpoints.length); gc.fillPolygon(xpoints, ypoints, xpoints.length);
//Render boundary to image.
this.background = snapshot(null, null);
} }
/** /**
@ -511,18 +462,6 @@ public class ResizableRaceCanvas extends ResizableCanvas {
//Marks. //Marks.
drawMarks(); drawMarks();
//Wind arrow. This rotates the wind arrow node.
displayWindArrow(this.visualiserRace.getWindDirection().degrees());
}
/**
* Draws the race boundary image onto the canvas.
* See {@link #background}.
*/
private void drawBoundary() {
gc.drawImage(this.background, 0, 0);
} }

@ -50,4 +50,6 @@
.scroll-bar > .increment-button:pressed > .increment-arrow, .scroll-bar > .increment-button:pressed > .increment-arrow,
.scroll-bar > .decrement-button:pressed > .decrement-arrow { .scroll-bar > .decrement-button:pressed > .decrement-arrow {
-fx-background-color: -fx-mark-highlight-color, rgb(255, 255, 255); -fx-background-color: -fx-mark-highlight-color, rgb(255, 255, 255);
} }

@ -51,4 +51,9 @@
.scroll-bar > .increment-button:pressed > .increment-arrow, .scroll-bar > .increment-button:pressed > .increment-arrow,
.scroll-bar > .decrement-button:pressed > .decrement-arrow { .scroll-bar > .decrement-button:pressed > .decrement-arrow {
-fx-background-color: -fx-mark-highlight-color, rgb(255, 255, 255); -fx-background-color: -fx-mark-highlight-color, rgb(255, 255, 255);
} }
#arrowImage {
-fx-image: url("/visualiser/images/arrowLight.png");
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

@ -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">

@ -0,0 +1,111 @@
package mock.model;
import org.junit.Before;
import org.junit.Test;
import shared.model.Bearing;
import shared.model.Wind;
import static org.junit.Assert.*;
public class WindGeneratorTest {
private WindGenerator windGenerator;
private Bearing windBaselineBearing;
private Bearing windBearingLowerBound;
private Bearing windBearingUpperBound;
private double windBaselineSpeed;
private double windSpeedLowerBound;
private double windSpeedUpperBound;
private double speedKnotsEpsilon;
private double bearingDegreeEpsilon;
@Before
public void setUp() throws Exception {
//Bounds.
this.windBaselineBearing = Bearing.fromDegrees(88.3);
this.windBearingLowerBound = Bearing.fromDegrees(66.5);
this.windBearingUpperBound = Bearing.fromDegrees(248.6);
this.windBaselineSpeed = 13;
this.windSpeedLowerBound = 7;
this.windSpeedUpperBound = 20;
this.windGenerator = new WindGenerator(
windBaselineBearing,
windBearingLowerBound,
windBearingUpperBound,
windBaselineSpeed,
windSpeedLowerBound,
windSpeedUpperBound );
this.bearingDegreeEpsilon = 0.001;
this.speedKnotsEpsilon = 0.001;
}
/**
* Tests if the baseline wind generated it accurate.
*/
@Test
public void generateBaselineWindTest() {
Wind wind = windGenerator.generateBaselineWind();
assertEquals(windBaselineSpeed, wind.getWindSpeed(), speedKnotsEpsilon);
assertEquals(windBaselineBearing.degrees(), wind.getWindDirection().degrees(), bearingDegreeEpsilon);
}
/**
* Tests if the random wind generated is inside the bounds.
*/
@Test
public void generateRandomWindTest() {
int randomWindCount = 1000;
for (int i = 0; i < randomWindCount; i++) {
Wind wind = windGenerator.generateRandomWind();
assertTrue(wind.getWindSpeed() >= windSpeedLowerBound);
assertTrue(wind.getWindSpeed() <= windSpeedUpperBound);
assertTrue(wind.getWindDirection().degrees() >= windBearingLowerBound.degrees());
assertTrue(wind.getWindDirection().degrees() <= windBearingUpperBound.degrees());
}
}
/**
* Tests if the next wind generated is inside the bounds.
*/
@Test
public void generateNextWindTest() {
Wind wind = windGenerator.generateBaselineWind();
int randomWindCount = 1000;
for (int i = 0; i < randomWindCount; i++) {
wind = windGenerator.generateNextWind(wind);
assertTrue(wind.getWindSpeed() >= windSpeedLowerBound);
assertTrue(wind.getWindSpeed() <= windSpeedUpperBound);
assertTrue(wind.getWindDirection().degrees() >= windBearingLowerBound.degrees());
assertTrue(wind.getWindDirection().degrees() <= windBearingUpperBound.degrees());
}
}
}
Loading…
Cancel
Save