|
|
|
|
@ -1,5 +1,6 @@
|
|
|
|
|
package visualiser.Controllers;
|
|
|
|
|
|
|
|
|
|
import com.interactivemesh.jfx.importer.stl.StlMeshImporter;
|
|
|
|
|
import javafx.animation.AnimationTimer;
|
|
|
|
|
import javafx.application.Platform;
|
|
|
|
|
import javafx.collections.FXCollections;
|
|
|
|
|
@ -11,20 +12,29 @@ import javafx.scene.chart.LineChart;
|
|
|
|
|
import javafx.scene.control.*;
|
|
|
|
|
import javafx.scene.input.KeyCode;
|
|
|
|
|
import javafx.scene.input.KeyEvent;
|
|
|
|
|
import javafx.scene.layout.AnchorPane;
|
|
|
|
|
import javafx.scene.layout.GridPane;
|
|
|
|
|
import javafx.scene.layout.StackPane;
|
|
|
|
|
import javafx.scene.shape.MeshView;
|
|
|
|
|
import javafx.scene.shape.Sphere;
|
|
|
|
|
import javafx.scene.transform.Translate;
|
|
|
|
|
import javafx.util.Callback;
|
|
|
|
|
import network.Messages.Enums.RaceStatusEnum;
|
|
|
|
|
import shared.dataInput.RaceDataSource;
|
|
|
|
|
import shared.model.Leg;
|
|
|
|
|
import shared.model.Mark;
|
|
|
|
|
import visualiser.app.App;
|
|
|
|
|
import visualiser.gameController.ControllerClient;
|
|
|
|
|
import visualiser.gameController.Keys.ControlKey;
|
|
|
|
|
import visualiser.gameController.Keys.KeyFactory;
|
|
|
|
|
import visualiser.model.*;
|
|
|
|
|
|
|
|
|
|
import visualiser.layout.Subject3D;
|
|
|
|
|
import visualiser.layout.View3D;
|
|
|
|
|
import visualiser.model.Sparkline;
|
|
|
|
|
import visualiser.model.VisualiserBoat;
|
|
|
|
|
import visualiser.model.VisualiserRaceEvent;
|
|
|
|
|
import visualiser.model.VisualiserRaceState;
|
|
|
|
|
import visualiser.utils.GPSConverter;
|
|
|
|
|
import java.io.IOException;
|
|
|
|
|
import java.util.List;
|
|
|
|
|
import java.net.URL;
|
|
|
|
|
import java.util.Optional;
|
|
|
|
|
import java.util.logging.Level;
|
|
|
|
|
import java.util.logging.Logger;
|
|
|
|
|
@ -36,15 +46,16 @@ public class RaceViewController extends Controller {
|
|
|
|
|
private VisualiserRaceEvent visualiserRace;
|
|
|
|
|
private VisualiserRaceState raceState;
|
|
|
|
|
private ControllerClient controllerClient;
|
|
|
|
|
private ResizableRaceCanvas raceCanvas;
|
|
|
|
|
private KeyFactory keyFactory = new KeyFactory();
|
|
|
|
|
private boolean infoTableShow = true; // shown or hidden
|
|
|
|
|
private boolean isHost;
|
|
|
|
|
private View3D view3D;
|
|
|
|
|
private ObservableList<Subject3D> viewSubjects;
|
|
|
|
|
|
|
|
|
|
// note: it says it's not used but it is! do not remove :)
|
|
|
|
|
private @FXML ArrowController arrowController;
|
|
|
|
|
private @FXML GridPane canvasBase;
|
|
|
|
|
private @FXML SplitPane race;
|
|
|
|
|
private @FXML SplitPane racePane;
|
|
|
|
|
private @FXML StackPane arrowPane;
|
|
|
|
|
private @FXML Label timer;
|
|
|
|
|
private @FXML Label FPS;
|
|
|
|
|
@ -56,7 +67,6 @@ public class RaceViewController extends Controller {
|
|
|
|
|
private @FXML TableColumn<VisualiserBoat, Leg> boatMarkColumn;
|
|
|
|
|
private @FXML TableColumn<VisualiserBoat, Number> boatSpeedColumn;
|
|
|
|
|
private @FXML LineChart<Number, Number> sparklineChart;
|
|
|
|
|
private @FXML AnchorPane annotationPane;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Displays a specified race.
|
|
|
|
|
@ -79,8 +89,8 @@ public class RaceViewController extends Controller {
|
|
|
|
|
/**
|
|
|
|
|
* Sets up the listener and actions that occur when a key is pressed.
|
|
|
|
|
*/
|
|
|
|
|
public void initKeypressHandler() {
|
|
|
|
|
race.addEventFilter(KeyEvent.KEY_PRESSED, event -> {
|
|
|
|
|
private void initKeypressHandler() {
|
|
|
|
|
racePane.addEventFilter(KeyEvent.KEY_PRESSED, event -> {
|
|
|
|
|
String codeString = event.getCode().toString();
|
|
|
|
|
|
|
|
|
|
// tab key
|
|
|
|
|
@ -124,7 +134,7 @@ public class RaceViewController extends Controller {
|
|
|
|
|
e.printStackTrace();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
@ -134,23 +144,82 @@ public class RaceViewController extends Controller {
|
|
|
|
|
// initialise displays
|
|
|
|
|
initialiseFps();
|
|
|
|
|
initialiseInfoTable();
|
|
|
|
|
initialiseRaceCanvas();
|
|
|
|
|
initialiseView3D();
|
|
|
|
|
initialiseRaceClock();
|
|
|
|
|
raceTimer(); // start the timer
|
|
|
|
|
new Annotations(this.annotationPane, this.raceCanvas);
|
|
|
|
|
new Sparkline(this.raceState, this.sparklineChart);
|
|
|
|
|
timeZone.setText(this.raceState.getRaceClock().getTimeZone());
|
|
|
|
|
arrowController.setWindProperty(this.raceState.windProperty());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void initialiseView3D() {
|
|
|
|
|
List<VisualiserBoat> boats = raceState.getBoats();
|
|
|
|
|
for(VisualiserBoat boat: boats) {
|
|
|
|
|
boat.positionProperty().addListener((o, prev, curr) -> {
|
|
|
|
|
System.out.println(boat.getCountry() + " is at " + curr.toString());
|
|
|
|
|
});
|
|
|
|
|
viewSubjects = FXCollections.observableArrayList();
|
|
|
|
|
|
|
|
|
|
// Import boat mesh
|
|
|
|
|
URL asset = RaceViewController.class.getClassLoader().getResource("assets/V1.2 " +
|
|
|
|
|
"Complete Boat.stl");
|
|
|
|
|
StlMeshImporter importer = new StlMeshImporter();
|
|
|
|
|
importer.read(asset);
|
|
|
|
|
|
|
|
|
|
// Configure camera angles and control
|
|
|
|
|
view3D = new View3D();
|
|
|
|
|
view3D.setDistance(1050);
|
|
|
|
|
view3D.setYaw(0);
|
|
|
|
|
view3D.setPitch(60);
|
|
|
|
|
view3D.enableTracking();
|
|
|
|
|
canvasBase.add(view3D, 0, 0);
|
|
|
|
|
|
|
|
|
|
// Set up projection from GPS to view
|
|
|
|
|
RaceDataSource raceData = raceState.getRaceDataSource();
|
|
|
|
|
final GPSConverter gpsConverter = new GPSConverter(raceData, 450, 450);
|
|
|
|
|
|
|
|
|
|
view3D.setItems(viewSubjects);
|
|
|
|
|
// Position and add each mark to view
|
|
|
|
|
for (Mark mark : raceState.getMarks()) {
|
|
|
|
|
Subject3D subject = new Subject3D(new Sphere(2));
|
|
|
|
|
subject.setX(gpsConverter.convertGPS(mark.getPosition()).getX());
|
|
|
|
|
subject.setZ(gpsConverter.convertGPS(mark.getPosition()).getY());
|
|
|
|
|
|
|
|
|
|
viewSubjects.add(subject);
|
|
|
|
|
}
|
|
|
|
|
// Position and add each boat to view
|
|
|
|
|
for (VisualiserBoat boat : raceState.getBoats()) {
|
|
|
|
|
MeshView mesh = new MeshView(importer.getImport());
|
|
|
|
|
Subject3D subject = new Subject3D(mesh);
|
|
|
|
|
viewSubjects.add(subject);
|
|
|
|
|
|
|
|
|
|
// Track this boat's movement with the new subject
|
|
|
|
|
AnimationTimer trackBoat = new AnimationTimer() {
|
|
|
|
|
@Override public void handle(long now) {
|
|
|
|
|
subject.setHeading(boat.getBearing().degrees());
|
|
|
|
|
subject.setX(gpsConverter.convertGPS(boat.getPosition()).getX());
|
|
|
|
|
subject.setZ(gpsConverter.convertGPS(boat.getPosition()).getY());
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
trackBoat.start();
|
|
|
|
|
}
|
|
|
|
|
// Fix initial bird's-eye position
|
|
|
|
|
view3D.updatePivot(new Translate(250, 0, 210));
|
|
|
|
|
|
|
|
|
|
// Bind zooming to scrolling
|
|
|
|
|
view3D.setOnScroll(e -> {
|
|
|
|
|
view3D.updateDistance(e.getDeltaY());
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Bind zooming to keypress (Z/X default)
|
|
|
|
|
racePane.addEventFilter(KeyEvent.KEY_PRESSED, e -> {
|
|
|
|
|
ControlKey key = keyFactory.getKey(e.getCode().toString());
|
|
|
|
|
if (key != null) {
|
|
|
|
|
switch (key.toString()) {
|
|
|
|
|
case "Zoom In":
|
|
|
|
|
view3D.updateDistance(-10);
|
|
|
|
|
break;
|
|
|
|
|
case "Zoom Out":
|
|
|
|
|
view3D.updateDistance(10);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
@ -178,7 +247,7 @@ public class RaceViewController extends Controller {
|
|
|
|
|
/**
|
|
|
|
|
* Initialises the information table view to listen to a given race.
|
|
|
|
|
*/
|
|
|
|
|
public void initialiseInfoTable() {
|
|
|
|
|
private void initialiseInfoTable() {
|
|
|
|
|
// list of boats to display data for
|
|
|
|
|
ObservableList<VisualiserBoat> boats = FXCollections
|
|
|
|
|
.observableArrayList(this.visualiserRace.getVisualiserRaceState().getBoats());
|
|
|
|
|
@ -243,23 +312,6 @@ public class RaceViewController extends Controller {
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Initialises the {@link ResizableRaceCanvas}, provides the race to
|
|
|
|
|
* read data from.
|
|
|
|
|
*/
|
|
|
|
|
private void initialiseRaceCanvas() {
|
|
|
|
|
//Create canvas.
|
|
|
|
|
raceCanvas = new ResizableRaceCanvas(raceState);
|
|
|
|
|
raceCanvas.setMouseTransparent(true);
|
|
|
|
|
raceCanvas.widthProperty().bind(canvasBase.widthProperty());
|
|
|
|
|
raceCanvas.heightProperty().bind(canvasBase.heightProperty());
|
|
|
|
|
|
|
|
|
|
// draw and display
|
|
|
|
|
raceCanvas.draw();
|
|
|
|
|
raceCanvas.setVisible(true);
|
|
|
|
|
canvasBase.getChildren().add(0, raceCanvas);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Initialises the race clock to listen to the specified race.
|
|
|
|
|
*/
|
|
|
|
|
@ -275,7 +327,7 @@ public class RaceViewController extends Controller {
|
|
|
|
|
/**
|
|
|
|
|
* Transition from the race view to the finish view.
|
|
|
|
|
*/
|
|
|
|
|
public void finishRace() throws IOException {
|
|
|
|
|
private void finishRace() throws IOException {
|
|
|
|
|
RaceFinishController fc =
|
|
|
|
|
(RaceFinishController)loadScene("raceFinish.fxml");
|
|
|
|
|
fc.loadFinish(raceState.getBoats());
|
|
|
|
|
@ -298,7 +350,7 @@ public class RaceViewController extends Controller {
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
// refresh visual race display
|
|
|
|
|
raceCanvas.drawRace();
|
|
|
|
|
// raceCanvas.drawRace();
|
|
|
|
|
boatInfoTable.sort();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@ -320,16 +372,18 @@ public class RaceViewController extends Controller {
|
|
|
|
|
* toggles if the info table is shown
|
|
|
|
|
*/
|
|
|
|
|
private void toggleTable() {
|
|
|
|
|
double tablePercent = 1 - (boatPlacingColumn.getPrefWidth() + boatTeamColumn.getPrefWidth() + boatMarkColumn.getPrefWidth() + boatSpeedColumn.getPrefWidth())/race.getWidth();
|
|
|
|
|
double tablePercent = 1 - (boatPlacingColumn.getPrefWidth() +
|
|
|
|
|
boatTeamColumn.getPrefWidth() + boatMarkColumn.getPrefWidth()
|
|
|
|
|
+ boatSpeedColumn.getPrefWidth())/racePane.getWidth();
|
|
|
|
|
|
|
|
|
|
if (infoTableShow) {
|
|
|
|
|
race.setDividerPositions(tablePercent);
|
|
|
|
|
racePane.setDividerPositions(tablePercent);
|
|
|
|
|
arrowPane.setScaleX(0.5);
|
|
|
|
|
arrowPane.setScaleY(0.5);
|
|
|
|
|
arrowPane.setTranslateX(0 + (arrowPane.getScene().getWidth()/4)*tablePercent);
|
|
|
|
|
arrowPane.setTranslateY(0 - arrowPane.getScene().getHeight()/4);
|
|
|
|
|
} else {
|
|
|
|
|
race.setDividerPositions(1);
|
|
|
|
|
racePane.setDividerPositions(1);
|
|
|
|
|
arrowPane.setScaleX(1);
|
|
|
|
|
arrowPane.setScaleY(1);
|
|
|
|
|
arrowPane.setTranslateX(0);
|
|
|
|
|
|