diff --git a/racevisionGame/src/main/java/mock/app/Event.java b/racevisionGame/src/main/java/mock/app/Event.java index 84481ce0..f8177612 100644 --- a/racevisionGame/src/main/java/mock/app/Event.java +++ b/racevisionGame/src/main/java/mock/app/Event.java @@ -18,7 +18,6 @@ import shared.exceptions.InvalidRegattaDataException; import shared.exceptions.XMLReaderException; import shared.model.Bearing; import shared.model.Constants; -import shared.xml.XMLUtilities; import java.io.IOException; import java.nio.charset.StandardCharsets; diff --git a/racevisionGame/src/main/java/mock/xml/RaceXMLCreator.java b/racevisionGame/src/main/java/mock/xml/RaceXMLCreator.java index b168862f..6c098ebe 100644 --- a/racevisionGame/src/main/java/mock/xml/RaceXMLCreator.java +++ b/racevisionGame/src/main/java/mock/xml/RaceXMLCreator.java @@ -1,34 +1,22 @@ package mock.xml; -import org.w3c.dom.Document; import org.xml.sax.SAXException; import shared.dataInput.RaceXMLReader; import shared.enums.XMLFileType; import shared.exceptions.InvalidRaceDataException; import shared.exceptions.XMLReaderException; -import shared.model.*; -import shared.xml.Race.*; +import shared.model.CompoundMark; +import shared.model.Constants; +import shared.model.GPSCoordinate; +import shared.xml.Race.XMLCompoundMark; +import shared.xml.Race.XMLLimit; +import shared.xml.Race.XMLMark; +import shared.xml.Race.XMLRace; import shared.xml.XMLUtilities; -import javax.xml.XMLConstants; -import javax.xml.bind.JAXBContext; import javax.xml.bind.JAXBException; -import javax.xml.bind.Marshaller; -import javax.xml.bind.Unmarshaller; -import javax.xml.bind.util.JAXBSource; -import javax.xml.parsers.DocumentBuilder; -import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; -import javax.xml.transform.Source; -import javax.xml.transform.dom.DOMSource; -import javax.xml.validation.Schema; -import javax.xml.validation.SchemaFactory; -import javax.xml.validation.Validator; -import java.io.File; import java.io.IOException; -import java.io.StringWriter; -import java.math.BigInteger; -import java.net.URL; import java.time.ZonedDateTime; import java.time.format.DateTimeFormatter; diff --git a/racevisionGame/src/main/java/visualiser/Controllers/Controller.java b/racevisionGame/src/main/java/visualiser/Controllers/Controller.java index 23094fd7..6d14b32d 100644 --- a/racevisionGame/src/main/java/visualiser/Controllers/Controller.java +++ b/racevisionGame/src/main/java/visualiser/Controllers/Controller.java @@ -7,22 +7,25 @@ import javafx.scene.image.Image; import javafx.stage.Modality; import javafx.stage.Stage; import visualiser.app.App; - import java.io.IOException; - /** * Abstract controller class to give each subclass the functionality to load * a new scene into the existing stage, or create a new popup window. */ public abstract class Controller { - Stage stage = App.getStage(); + private Stage stage = App.getStage(); + /** + * Loads the title screen again when app is already running. + * @throws IOException if a problem with the title.fxml + */ protected void loadTitleScreen() throws IOException { FXMLLoader loader = new FXMLLoader(getClass().getResource("/visualiser/scenes/title.fxml")); Parent root = loader.load(); stage.setResizable(false); Scene scene = new Scene(root); + addCssStyle(scene); stage.setScene(scene); stage.show(); } @@ -47,6 +50,7 @@ public abstract class Controller { // set new scene into existing window Scene scene = new Scene(root, stageWidth, stageHeight); + addCssStyle(scene); stage.setScene(scene); stage.setResizable(true); stage.show(); @@ -81,6 +85,7 @@ public abstract class Controller { stage.centerOnScreen(); stage.getIcons().add(new Image(getClass().getClassLoader().getResourceAsStream("images/SailIcon.png"))); Scene scene = new Scene(root); + addCssStyle(scene); stage.setScene(scene); stage.show(); @@ -88,4 +93,16 @@ public abstract class Controller { return loader.getController(); } + /** + * Adds the relevant CSS styling to the scene being loaded. + * @param scene new scene to be loaded and displayed + */ + private void addCssStyle(Scene scene){ + if (App.dayMode) { + scene.getStylesheets().add("/css/dayMode.css"); + } else { + scene.getStylesheets().add("/css/nightMode.css"); + } + } + } diff --git a/racevisionGame/src/main/java/visualiser/Controllers/HostGameController.java b/racevisionGame/src/main/java/visualiser/Controllers/HostGameController.java index b5c9e4b8..dcd2a42d 100644 --- a/racevisionGame/src/main/java/visualiser/Controllers/HostGameController.java +++ b/racevisionGame/src/main/java/visualiser/Controllers/HostGameController.java @@ -2,15 +2,13 @@ package visualiser.Controllers; import javafx.application.Platform; import javafx.fxml.FXML; -import javafx.scene.control.*; +import javafx.scene.control.Alert; +import javafx.scene.control.ButtonType; import javafx.scene.image.Image; import javafx.scene.image.ImageView; -import javafx.scene.layout.AnchorPane; -import javafx.stage.Stage; import mock.app.Event; import mock.exceptions.EventConstructionException; import visualiser.app.App; - import java.io.IOException; import java.net.Socket; import java.util.ArrayList; @@ -22,38 +20,31 @@ import java.util.logging.Logger; /** * Controller for Hosting a game. */ -public class HostController extends Controller { - private @FXML ImageView imageView; - private @FXML AnchorPane imagePane; - private @FXML SplitPane splitPane; - private @FXML AnchorPane specPane; - @FXML ImageView mapImage; +public class HostGameController extends Controller { + private @FXML ImageView mapImage; private ArrayList listOfMaps; private int currentMapIndex = 0; - @FXML TextField gameNameField; - - @FXML TextField hostNameField; - @FXML Button previousButton; - - @FXML Button nextButton; public void initialize() { + loadMaps(); + } + + /** + * Loads in the list of playable maps to be selected from. + */ + private void loadMaps(){ + // image preview of maps Image ac35Map = new Image(getClass().getClassLoader().getResourceAsStream("images/AC35_Racecourse_MAP.png")); Image oMap = new Image(getClass().getClassLoader().getResourceAsStream("images/oMapLayout.png")); Image iMap = new Image(getClass().getClassLoader().getResourceAsStream("images/iMapLayout.png")); Image mMap = new Image(getClass().getClassLoader().getResourceAsStream("images/mMapLayout.png")); listOfMaps = new ArrayList(Arrays.asList(ac35Map, oMap, iMap, mMap)); -// mapImage.setImage(listOfMaps.get(currentMapIndex)); + mapImage.setImage(listOfMaps.get(currentMapIndex)); Platform.runLater(() -> { - mapImage.setImage(listOfMaps.get(currentMapIndex)); + mapImage.fitWidthProperty() + .bind(mapImage.getScene().getWindow().widthProperty().multiply(0.6)); }); - hostGame(); - - - // mapImage..addListener( -// (observable, oldValue, newValue) -> Platform -// .runLater(() -> )); } /** @@ -76,29 +67,12 @@ public class HostController extends Controller { * @param address address of the server * @param port port that the server is run off */ - public void connectSocket(String address, int port) throws IOException { + private void connectSocket(String address, int port) throws IOException { Socket socket = new Socket(address, port); RaceStartController rsc = (RaceStartController)loadScene("raceStart.fxml"); rsc.enterLobby(socket, true); } - /** - * Hosts a game. - */ - public void hostGame(){ -// splitPane.setResizableWithParent(specPane, false); -// splitPane.lookupAll(".split-pane-divider").stream().forEach(div -> div.setMouseTransparent(true)); -// imageView.fitWidthProperty().bind(imagePane.widthProperty()); -// imageView.fitHeightProperty().bind(imagePane.heightProperty()); - - Platform.runLater(() -> { - mapImage.fitWidthProperty() - .bind(((Stage)mapImage.getScene().getWindow()).widthProperty().multiply(0.6)); - - }); -// mapImage.fitWidthProperty().bind(((Stage) mapImage.getScene().getWindow()).widthProperty().multiply(0.6)); - } - /** * Menu button pressed. Prompt alert then return to menu */ @@ -110,32 +84,30 @@ public class HostController extends Controller { Optional result = alert.showAndWait(); if(result.get() == ButtonType.OK){ loadTitleScreen(); - }} + } + } /** - * Start button pressed. Currently only prints out start + * Method called when the 'next' arrow button is pressed. It is used to + * change the currently displayed map preview to the next in the list. */ - public void startBtnPressed() { - //System.out.println("Should start the race. This button is only visible for the host"); - hostGamePressed(); - } - - public void nextImage(){ - increaseIndex(); + // increase index + currentMapIndex = (currentMapIndex + 1) % listOfMaps.size(); + // update map preview mapImage.setImage(listOfMaps.get(currentMapIndex)); } + /** + * Method called when the 'previous' arrow button is pressed. It is used to + * change the currently displayed map preview to the previous in the list. + */ public void previousImage(){ - decreaseIndex(); + // decrease index + currentMapIndex = ((((currentMapIndex - 1) % listOfMaps.size()) + + listOfMaps.size()) % listOfMaps.size()); + // update map preview mapImage.setImage(listOfMaps.get(currentMapIndex)); } - private void increaseIndex(){ - currentMapIndex = (currentMapIndex + 1)%listOfMaps.size(); - } - - private void decreaseIndex(){ - currentMapIndex = ((((currentMapIndex - 1)%listOfMaps.size())+listOfMaps.size())%listOfMaps.size()); - } } diff --git a/racevisionGame/src/main/java/visualiser/Controllers/KeyBindingsController.java b/racevisionGame/src/main/java/visualiser/Controllers/KeyBindingsController.java index b8359aad..617af614 100644 --- a/racevisionGame/src/main/java/visualiser/Controllers/KeyBindingsController.java +++ b/racevisionGame/src/main/java/visualiser/Controllers/KeyBindingsController.java @@ -218,7 +218,7 @@ public class KeyBindingsController extends Controller { private Boolean isFactoryValid(){ for (Map.Entry entry : newKeyFactory.getKeyState().entrySet ()) { - if (entry.getKey().toString().equals(entry.getValue().toString())){ + if (entry.getKey().equals(entry.getValue().toString())){ return false; } } diff --git a/racevisionGame/src/main/java/visualiser/Controllers/LobbyController.java b/racevisionGame/src/main/java/visualiser/Controllers/LobbyController.java index 6a4fc447..3f416536 100644 --- a/racevisionGame/src/main/java/visualiser/Controllers/LobbyController.java +++ b/racevisionGame/src/main/java/visualiser/Controllers/LobbyController.java @@ -8,7 +8,6 @@ import javafx.scene.control.TableColumn; import javafx.scene.control.TableView; import javafx.scene.control.TextField; import visualiser.model.RaceConnection; - import java.io.IOException; import java.net.Socket; diff --git a/racevisionGame/src/main/java/visualiser/Controllers/RaceViewController.java b/racevisionGame/src/main/java/visualiser/Controllers/RaceViewController.java index 1b787e2f..48b02f41 100644 --- a/racevisionGame/src/main/java/visualiser/Controllers/RaceViewController.java +++ b/racevisionGame/src/main/java/visualiser/Controllers/RaceViewController.java @@ -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 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 boatMarkColumn; private @FXML TableColumn boatSpeedColumn; private @FXML LineChart 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 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 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); diff --git a/racevisionGame/src/main/java/visualiser/Controllers/TitleController.java b/racevisionGame/src/main/java/visualiser/Controllers/TitleController.java index 68fefe98..20d5bb9a 100644 --- a/racevisionGame/src/main/java/visualiser/Controllers/TitleController.java +++ b/racevisionGame/src/main/java/visualiser/Controllers/TitleController.java @@ -3,6 +3,7 @@ package visualiser.Controllers; import javafx.fxml.FXML; import javafx.scene.control.RadioButton; import javafx.stage.Modality; +import visualiser.app.App; import java.io.IOException; @@ -23,7 +24,7 @@ public class TitleController extends Controller { * @throws IOException if main has problems */ public void hostAGame() throws IOException { - loadScene("lobbyHosting.fxml"); + loadScene("hostGame.fxml"); } /** @@ -41,6 +42,7 @@ public class TitleController extends Controller { dayModeRD.getScene().getStylesheets().clear(); dayModeRD.getScene().getStylesheets().add("/css/dayMode.css"); nightModeRD.setSelected(false); + App.dayMode = true; } /** @@ -50,6 +52,7 @@ public class TitleController extends Controller { nightModeRD.getScene().getStylesheets().clear(); nightModeRD.getScene().getStylesheets().add("/css/nightMode.css"); dayModeRD.setSelected(false); + App.dayMode = false; } /** diff --git a/racevisionGame/src/main/java/visualiser/app/App.java b/racevisionGame/src/main/java/visualiser/app/App.java index c8c8d1f3..ccb25d07 100644 --- a/racevisionGame/src/main/java/visualiser/app/App.java +++ b/racevisionGame/src/main/java/visualiser/app/App.java @@ -7,7 +7,6 @@ import javafx.collections.FXCollections; import javafx.collections.ObservableList; import javafx.concurrent.Task; import javafx.concurrent.Worker; -import javafx.event.EventHandler; import javafx.fxml.FXMLLoader; import javafx.geometry.Pos; import javafx.geometry.Rectangle2D; @@ -24,20 +23,19 @@ import javafx.scene.paint.Color; import javafx.stage.Screen; import javafx.stage.Stage; import javafx.stage.StageStyle; -import javafx.stage.WindowEvent; import javafx.util.Duration; import mock.app.Event; public class App extends Application { private static Stage stage; public static Event game; + public static Boolean dayMode = true; private Pane splashLayout; private ProgressBar loadProgress; private Label progressText; private static final int SPLASH_WIDTH = 676; private static final int SPLASH_HEIGHT = 227; - /** * Entry point for running the programme * @param args for starting the programme @@ -73,7 +71,7 @@ public class App extends Application { /** * Method that sets up and displays the splash screen - * @param stage the inital stage + * @param stage the initial stage * @throws Exception if something wrong with title screen. */ public void start(Stage stage) throws Exception { @@ -81,7 +79,7 @@ public class App extends Application { @Override protected ObservableList call() throws InterruptedException { ObservableList addedFilling = - FXCollections.observableArrayList(); + FXCollections.observableArrayList(); ObservableList burgerFilling = FXCollections.observableArrayList( "Buns", "Patties", "Lettuce", "Onions", "Tomato", @@ -124,7 +122,11 @@ public class App extends Application { return App.stage; } - public void loadTitleScreen() throws Exception { + /** + * Loads the title screen for the first time on app start. + * @throws Exception if there is a problem with a resource loaded + */ + private void loadTitleScreen() throws Exception { stage = new Stage(); FXMLLoader loader = new FXMLLoader(getClass().getResource ("/visualiser/scenes/title.fxml")); @@ -135,11 +137,9 @@ public class App extends Application { stage.getIcons().add(new Image(getClass().getClassLoader().getResourceAsStream("images/SailIcon.png"))); stage.setScene(scene); stage.show(); - stage.setOnCloseRequest(new EventHandler() { - @Override public void handle(WindowEvent event) { - Platform.exit(); - System.exit(0); - } + stage.setOnCloseRequest(event -> { + Platform.exit(); + System.exit(0); }); } diff --git a/racevisionGame/src/main/resources/visualiser/scenes/hostGame.fxml b/racevisionGame/src/main/resources/visualiser/scenes/hostGame.fxml index ab964820..cf82c285 100644 --- a/racevisionGame/src/main/resources/visualiser/scenes/hostGame.fxml +++ b/racevisionGame/src/main/resources/visualiser/scenes/hostGame.fxml @@ -10,7 +10,7 @@ - + diff --git a/racevisionGame/src/main/resources/visualiser/scenes/raceView.fxml b/racevisionGame/src/main/resources/visualiser/scenes/raceView.fxml index b6496743..8733a73e 100644 --- a/racevisionGame/src/main/resources/visualiser/scenes/raceView.fxml +++ b/racevisionGame/src/main/resources/visualiser/scenes/raceView.fxml @@ -22,7 +22,11 @@ - +