diff --git a/dedicatedServer/src/main/java/app/App.java b/dedicatedServer/src/main/java/app/App.java index 34ee2c5b..3b36c43e 100644 --- a/dedicatedServer/src/main/java/app/App.java +++ b/dedicatedServer/src/main/java/app/App.java @@ -21,7 +21,7 @@ public class App extends Application { public void start(Stage primaryStage) { try { //TODO should read a configuration file to configure server? - Event raceEvent = new Event(false); + Event raceEvent = new Event(false, 0); } catch (Exception e) { diff --git a/racevisionGame/src/main/java/mock/app/Event.java b/racevisionGame/src/main/java/mock/app/Event.java index 3e8c0727..84481ce0 100644 --- a/racevisionGame/src/main/java/mock/app/Event.java +++ b/racevisionGame/src/main/java/mock/app/Event.java @@ -18,6 +18,7 @@ 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; @@ -72,9 +73,24 @@ public class Event { * @param singlePlayer Whether or not to create a single player event. * @throws EventConstructionException Thrown if we cannot create an Event for any reason. */ - public Event(boolean singlePlayer) throws EventConstructionException { + public Event(boolean singlePlayer, int mapIndex) throws EventConstructionException { + +// System.out.println(XMLUtilities.validateXML(this.getClass().getClassLoader().getResource("mock/mockXML/iMapLayout.xml").toString() +// , this.getClass().getClassLoader().getResource("mock/mockXML/schema/raceSchema.xsd"))); + + String raceXMLFile; + switch (mapIndex){ + case 0:raceXMLFile = "mock/mockXML/raceSixPlayers.xml"; + break; + case 1:raceXMLFile = "mock/mockXML/oMapLayout.xml"; + break; + case 2: raceXMLFile = "mock/mockXML/iMapLayout.xml"; + break; + case 3: raceXMLFile = "mock/mockXML/mMapLayout.xml"; + break; + default: raceXMLFile = "mock/mockXML/raceSixPlayers.xml"; - String raceXMLFile = "mock/mockXML/raceTest.xml"; + } String boatsXMLFile = "mock/mockXML/boatTest.xml"; String regattaXMLFile = "mock/mockXML/regattaTest.xml"; @@ -173,7 +189,7 @@ public class Event { public static String setRaceXMLAtCurrentTimeToNow(String raceXML) { //The start time is current time + 4 minutes. prestart is 3 minutes, and we add another minute. - long millisecondsToAdd = Constants.RacePreStartTime + (1 * 60 * 1000); + long millisecondsToAdd = Constants.RacePreStartTime + Constants.RacePreparatoryTime; long secondsToAdd = millisecondsToAdd / 1000; //Scale the time using our time scalar. secondsToAdd = secondsToAdd / Constants.RaceTimeScale; diff --git a/racevisionGame/src/main/java/mock/model/SourceIdAllocator.java b/racevisionGame/src/main/java/mock/model/SourceIdAllocator.java index 17a7e85f..f590d9b7 100644 --- a/racevisionGame/src/main/java/mock/model/SourceIdAllocator.java +++ b/racevisionGame/src/main/java/mock/model/SourceIdAllocator.java @@ -42,6 +42,7 @@ public class SourceIdAllocator { } List allocatedIDs = mockRace.getRaceDataSource().getParticipants(); + List allIDs = new ArrayList<>(mockRace.getBoatDataSource().getBoats().keySet()); //Get list of unallocated ids. diff --git a/racevisionGame/src/main/java/mock/xml/RaceXMLCreator.java b/racevisionGame/src/main/java/mock/xml/RaceXMLCreator.java index 5cb74625..b168862f 100644 --- a/racevisionGame/src/main/java/mock/xml/RaceXMLCreator.java +++ b/racevisionGame/src/main/java/mock/xml/RaceXMLCreator.java @@ -187,7 +187,7 @@ public class RaceXMLCreator { public static void setRaceXMLAtCurrentTimeToNow(XMLRace raceXML) { //The start time is current time + 4 minutes. prestart is 3 minutes, and we add another minute. - long millisecondsToAdd = Constants.RacePreStartTime + (1 * 60 * 1000); + long millisecondsToAdd = Constants.RacePreStartTime + Constants.RacePreparatoryTime; long secondsToAdd = millisecondsToAdd / 1000; //Scale the time using our time scalar. secondsToAdd = secondsToAdd / Constants.RaceTimeScale; diff --git a/racevisionGame/src/main/java/shared/model/Constants.java b/racevisionGame/src/main/java/shared/model/Constants.java index bb7ec598..7a5e2820 100644 --- a/racevisionGame/src/main/java/shared/model/Constants.java +++ b/racevisionGame/src/main/java/shared/model/Constants.java @@ -36,20 +36,23 @@ public class Constants { public static final int RaceTimeScale = 2;//10; /** - * The race pre-start time, in milliseconds. 3 minutes. + * The race pre-start time, in milliseconds. 3 minutes (30 seconds for development). */ - public static final long RacePreStartTime = 3 * 60 * 1000; +// public static final long RacePreStartTime = 30 * 1000; + public static final long RacePreStartTime = 1000; /** * The race preparatory time, in milliseconds. 1 minute. */ +// public static final long RacePreparatoryTime = 60 * 1000; public static final long RacePreparatoryTime = 1 * 60 * 1000; + /** * The number of milliseconds in one hour. *
diff --git a/racevisionGame/src/main/java/visualiser/Controllers/ConnectionController.java b/racevisionGame/src/main/java/visualiser/Controllers/ConnectionController.java deleted file mode 100644 index 7c9269e3..00000000 --- a/racevisionGame/src/main/java/visualiser/Controllers/ConnectionController.java +++ /dev/null @@ -1,127 +0,0 @@ -package visualiser.Controllers; - -import javafx.collections.FXCollections; -import javafx.collections.ObservableList; -import javafx.fxml.FXML; -import javafx.scene.control.*; -import javafx.scene.layout.AnchorPane; -import visualiser.model.RaceConnection; - -import java.io.IOException; -import java.net.Socket; -import java.net.URL; -import java.util.ResourceBundle; - -//TODO it appears this view/controller was replaced by Lobby.fxml. Remove? -/** - * Controls the connection that the Visualiser can connect to. - */ -public class ConnectionController extends Controller { - @FXML AnchorPane connectionWrapper; - @FXML TableView connectionTable; - @FXML TableColumn hostnameColumn; - @FXML TableColumn statusColumn; - @FXML Button connectButton; - @FXML TextField urlField; - @FXML TextField portField; - - - /*Title Screen fxml items*/ - @FXML - private Button hostGameTitleBtn; - @FXML - private Button connectGameBtn; - @FXML - private RadioButton nightRadioBtn; - @FXML - private RadioButton dayRadioButton; - - /*Lobby fxml items*/ - @FXML - private TableView lobbyTable; - @FXML - private TableColumn gameNameColumn; - @FXML - private TableColumn hostNameColumn; - @FXML - private TableColumn playerCountColumn; - @FXML - private TextField playerNameField; - @FXML - private Button joinGameBtn; - - /*Host game fxml items*/ - @FXML - private TextField gameNameField; - @FXML - private TextField hostNameField; - @FXML - private TextField hostGameBtn; - - - - - - - - - private ObservableList connections; - - - - public void initialize(URL location, ResourceBundle resources) { - // TODO - replace with config file - connections = FXCollections.observableArrayList(); - - connectionTable.setItems(connections); - hostnameColumn.setCellValueFactory(cellData -> cellData.getValue().hostnameProperty()); - statusColumn.setCellValueFactory(cellData -> cellData.getValue().statusProperty()); - - connectionTable.getSelectionModel().selectedItemProperty().addListener((obs, prev, curr) -> { - if (curr != null && curr.check()) connectButton.setDisable(false); - else connectButton.setDisable(true); - }); - connectButton.setDisable(true); - } - - /** - * Sets current status of all connections. - */ - public void checkConnections() { - for(RaceConnection connection: connections) { - connection.check(); - } - } - - public AnchorPane startWrapper(){ - return connectionWrapper; - } - - /** - * Connects to host currently selected in table. Button enabled only if host is ready. - */ - public void connectSocket() { - try{ - RaceConnection connection = connectionTable.getSelectionModel().getSelectedItem(); - Socket socket = new Socket(connection.getHostname(), connection.getPort()); - socket.setKeepAlive(true); - connectionWrapper.setVisible(false); - //parent.enterLobby(socket); - } catch (IOException e) { /* Never reached */ } - } - - /** - * adds a new connection - */ - public void addConnection(){ - String hostName = urlField.getText(); - String portString = portField.getText(); - try{ - int port = Integer.parseInt(portString); - connections.add(new RaceConnection(hostName, port, null)); - }catch(NumberFormatException e){ - System.err.println("Port number entered is not a number"); - } - } - -} diff --git a/racevisionGame/src/main/java/visualiser/Controllers/HostGameController.java b/racevisionGame/src/main/java/visualiser/Controllers/HostGameController.java new file mode 100644 index 00000000..b5c9e4b8 --- /dev/null +++ b/racevisionGame/src/main/java/visualiser/Controllers/HostGameController.java @@ -0,0 +1,141 @@ +package visualiser.Controllers; + +import javafx.application.Platform; +import javafx.fxml.FXML; +import javafx.scene.control.*; +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; +import java.util.Arrays; +import java.util.Optional; +import java.util.logging.Level; +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; + private ArrayList listOfMaps; + private int currentMapIndex = 0; + @FXML TextField gameNameField; + + @FXML TextField hostNameField; + @FXML Button previousButton; + + @FXML Button nextButton; + + public void initialize() { + 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)); + Platform.runLater(() -> { + mapImage.setImage(listOfMaps.get(currentMapIndex)); + }); + hostGame(); + + + // mapImage..addListener( +// (observable, oldValue, newValue) -> Platform +// .runLater(() -> )); + } + + /** + * Hosts a game + */ + public void hostGamePressed() { + try { + App.game = new Event(false, currentMapIndex); + connectSocket("localhost", 4942); + } catch (EventConstructionException e) { + Logger.getGlobal().log(Level.SEVERE, "Could not create Event.", e); + throw new RuntimeException(e); + } catch (IOException e) { + e.printStackTrace(); + } + } + + /** + * Connect to a socket + * @param address address of the server + * @param port port that the server is run off + */ + public 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 + */ + public void menuBtnPressed() throws Exception { + Alert alert = new Alert(Alert.AlertType.CONFIRMATION); + alert.setTitle("Quitting race"); + alert.setContentText("Do you wish to quit the race?"); + alert.setHeaderText("You are about to quit the race"); + Optional result = alert.showAndWait(); + if(result.get() == ButtonType.OK){ + loadTitleScreen(); + }} + + /** + * Start button pressed. Currently only prints out start + */ + public void startBtnPressed() { + //System.out.println("Should start the race. This button is only visible for the host"); + hostGamePressed(); + } + + + public void nextImage(){ + increaseIndex(); + mapImage.setImage(listOfMaps.get(currentMapIndex)); + } + + public void previousImage(){ + decreaseIndex(); + 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/LobbyHostingController.java b/racevisionGame/src/main/java/visualiser/Controllers/LobbyHostingController.java deleted file mode 100644 index fdbfea6a..00000000 --- a/racevisionGame/src/main/java/visualiser/Controllers/LobbyHostingController.java +++ /dev/null @@ -1,127 +0,0 @@ -package visualiser.Controllers; - -import com.interactivemesh.jfx.importer.stl.StlMeshImporter; -import javafx.animation.AnimationTimer; -import javafx.collections.FXCollections; -import javafx.collections.ObservableList; -import javafx.fxml.FXML; -import javafx.scene.control.Alert; -import javafx.scene.control.ButtonType; -import javafx.scene.control.SplitPane; -import javafx.scene.image.ImageView; -import javafx.scene.layout.AnchorPane; -import javafx.scene.layout.GridPane; -import javafx.scene.shape.MeshView; -import mock.app.Event; -import mock.exceptions.EventConstructionException; -import visualiser.app.App; -import visualiser.layout.Subject3D; -import visualiser.layout.View3D; - -import java.io.IOException; -import java.net.Socket; -import java.net.URL; -import java.util.Optional; -import java.util.logging.Level; -import java.util.logging.Logger; - -/** - * Controller for Hosting a game. - */ -public class LobbyHostingController extends Controller { - private @FXML ImageView imageView; - private @FXML AnchorPane imagePane; - private @FXML SplitPane splitPane; - private @FXML AnchorPane specPane; - private @FXML GridPane playerContainer; - private View3D view3D; - - public void initialize() { - hostGame(); - ObservableList subjects = FXCollections.observableArrayList(); - - view3D = new View3D(); - view3D.setItems(subjects); - playerContainer.add(view3D, 0,0); - - URL asset = LobbyHostingController.class.getClassLoader().getResource("assets/V1.2 Complete Boat.stl"); - - StlMeshImporter importer = new StlMeshImporter(); - importer.read(asset); - Subject3D subject = new Subject3D(new MeshView(importer.getImport())); - - subjects.add(subject); - - view3D.setPivot(subject); - view3D.setDistance(50); - view3D.setYaw(45); - view3D.setPitch(20); - - AnimationTimer rotate = new AnimationTimer() { - @Override - public void handle(long now) { - subject.setHeading(subject.getHeading() + 0.1); - } - }; - rotate.start(); - } - - /** - * Hosts a game - */ - public void hostGamePressed() { - try { - App.game = new Event(false); - connectSocket("localhost", 4942); - } catch (EventConstructionException e) { - Logger.getGlobal().log(Level.SEVERE, "Could not create Event.", e); - throw new RuntimeException(e); - } catch (IOException e) { - e.printStackTrace(); - } - } - - /** - * Connect to a socket - * @param address address of the server - * @param port port that the server is run off - */ - public 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()); - } - - /** - * Menu button pressed. Prompt alert then return to menu - */ - public void menuBtnPressed() throws Exception { - Alert alert = new Alert(Alert.AlertType.CONFIRMATION); - alert.setTitle("Quitting race"); - alert.setContentText("Do you wish to quit the race?"); - alert.setHeaderText("You are about to quit the race"); - Optional result = alert.showAndWait(); - if(result.get() == ButtonType.OK){ - loadTitleScreen(); - } - } - - /** - * Start button pressed. Currently only prints out start - */ - public void startBtnPressed(){ - //System.out.println("Should start the race. This button is only visible for the host"); - hostGamePressed(); - } - -} diff --git a/racevisionGame/src/main/java/visualiser/gameController/Keys/ControlKey.java b/racevisionGame/src/main/java/visualiser/gameController/Keys/ControlKey.java index dd489f73..ce4b341e 100644 --- a/racevisionGame/src/main/java/visualiser/gameController/Keys/ControlKey.java +++ b/racevisionGame/src/main/java/visualiser/gameController/Keys/ControlKey.java @@ -1,6 +1,5 @@ package visualiser.gameController.Keys; -import javafx.scene.input.KeyCode; import network.Messages.Enums.BoatActionEnum; /** @@ -45,7 +44,7 @@ public abstract class ControlKey { /** * What this key should do when the command is issued for it to do its job. */ - public abstract void onAction();//may want to make it take in a visualiser and stuff in the future. + public abstract void onAction(); /** * What to do when the key is held diff --git a/racevisionGame/src/main/java/visualiser/layout/Subject3D.java b/racevisionGame/src/main/java/visualiser/layout/Subject3D.java index 17736843..af76f4f4 100644 --- a/racevisionGame/src/main/java/visualiser/layout/Subject3D.java +++ b/racevisionGame/src/main/java/visualiser/layout/Subject3D.java @@ -17,6 +17,7 @@ public class Subject3D { * Position translation updated by state listeners */ private Translate position; + /** * Heading rotation updated by state listeners */ @@ -31,42 +32,34 @@ public class Subject3D { this.position = new Translate(); this.heading = new Rotate(0, Rotate.Y_AXIS); - this.mesh.getTransforms().addAll(heading, new Rotate(-90, Rotate.X_AXIS), position); + this.mesh.getTransforms().addAll(position, heading, new Rotate(90, Rotate.X_AXIS), new Rotate(180, Rotate.Y_AXIS)); } public Shape3D getMesh() { return mesh; } - public double getX() { - return this.position.getX(); - } - - public double getY() { - return this.position.getY(); + public Translate getPosition() { + return this.position; } - public double getZ() { - return this.position.getZ(); + public Rotate getHeading() { + return heading; } public void setX(double x) { - this.position.setX(x); + position.setX(x); } public void setY(double y) { - this.position.setY(y); + position.setY(y); } public void setZ(double z) { - this.position.setZ(z); - } - - public double getHeading() { - return this.heading.getAngle(); + position.setZ(z); } public void setHeading(double angle) { - this.heading.setAngle(angle); + heading.setAngle(angle); } } diff --git a/racevisionGame/src/main/java/visualiser/layout/View3D.java b/racevisionGame/src/main/java/visualiser/layout/View3D.java index 2aae9f1a..27fe6086 100644 --- a/racevisionGame/src/main/java/visualiser/layout/View3D.java +++ b/racevisionGame/src/main/java/visualiser/layout/View3D.java @@ -1,26 +1,43 @@ package visualiser.layout; +import javafx.beans.value.ChangeListener; import javafx.collections.ListChangeListener; import javafx.collections.ObservableList; import javafx.scene.Group; import javafx.scene.PerspectiveCamera; import javafx.scene.SubScene; +import javafx.scene.input.PickResult; import javafx.scene.layout.Pane; import javafx.scene.paint.Color; import javafx.scene.shape.Shape3D; import javafx.scene.transform.Rotate; import javafx.scene.transform.Translate; +import java.util.HashMap; +import java.util.Map; + /** * Control for rendering 3D objects visible through a PerspectiveCamera. Implements Adapter Pattern to * interface with camera, and allows clients to add shapes to the scene. All scenes contain sea plane and * sky box, whose textures are set with special methods. */ public class View3D extends Pane { + /** + * Container for group and camera + */ + private SubScene scene; /** * Observable list of renderable items */ private ObservableList items; + /** + * Map for selecting Subject3D from Shape3D + */ + private Map selectionMap; + /** + * Subject tracked by camera + */ + private Subject3D target; /** * Rendering container for shapes */ @@ -34,7 +51,7 @@ public class View3D extends Pane { */ private double farClip; /** - * Position camera pivots around + * Camera origin */ private Translate pivot; /** @@ -49,17 +66,39 @@ public class View3D extends Pane { * Angle between ground plane and camera direction */ private Rotate pitch; + /** + * Single listener for subject heading changes + */ + private ChangeListener pivotHeading = (o, prev, curr) -> yaw.setAngle((double)curr); + /** + * Single listener for subject position (x) changes + */ + private ChangeListener pivotX = (o, prev, curr) -> pivot.setX((double)curr); + /** + * Single listener for subject position (y) changes + */ + private ChangeListener pivotY = (o, prev, curr) -> pivot.setY((double)curr); + /** + * Single listener for subject position (z) changes + */ + private ChangeListener pivotZ = (o, prev, curr) -> pivot.setZ((double)curr); + /** + * Distance to switch from third person to bird's eye + */ + private double THIRD_PERSON_LIMIT = 100; /** * Default constructor for View3D. Sets up Scene and PerspectiveCamera. */ public View3D() { - world = new Group(); + this.world = new Group(); + this.selectionMap = new HashMap<>(); + this.target = null; + this.scene = new SubScene(world, 300, 300); - SubScene scene = new SubScene(world, 300, 300); scene.widthProperty().bind(this.widthProperty()); scene.heightProperty().bind(this.heightProperty()); - scene.setFill(Color.BLACK); + scene.setFill(new Color(0.2, 0.6, 1, 1)); scene.setCamera(buildCamera()); @@ -75,7 +114,7 @@ public class View3D extends Pane { // Set up view frustum nearClip = 0.1; - farClip = 1000.0; + farClip = 3000.0; camera.setNearClip(nearClip); camera.setFarClip(farClip); @@ -89,18 +128,73 @@ public class View3D extends Pane { return camera; } + /** + * Provide the list of subjects to be automatically added or removed from the view as the list + * changes. + * @param items list managed by client + */ public void setItems(ObservableList items) { this.items = items; this.items.addListener((ListChangeListener) c -> { while(c.next()) { if (c.wasRemoved() || c.wasAdded()) { - for (Subject3D shape : c.getRemoved()) world.getChildren().remove(shape.getMesh()); - for (Subject3D shape : c.getAddedSubList()) world.getChildren().add(shape.getMesh()); + for (Subject3D shape : c.getRemoved()) { + world.getChildren().remove(shape.getMesh()); + selectionMap.remove(shape.getMesh()); + } + for (Subject3D shape : c.getAddedSubList()) { + world.getChildren().add(shape.getMesh()); + selectionMap.put(shape.getMesh(), shape); + } } } }); } + /** + * Intercept mouse clicks on subjects in view. The applied listener cannot be removed. + */ + public void enableTracking() { + scene.setOnMousePressed(e -> { + PickResult result = e.getPickResult(); + if(result != null && result.getIntersectedNode() != null && result.getIntersectedNode() instanceof Shape3D) { + trackSubject(selectionMap.get(result.getIntersectedNode())); + } + }); + } + + /** + * Stop camera from following the last selected subject + */ + private void untrackSubject() { + if(target != null) { + target.getPosition().xProperty().removeListener(pivotX); + target.getPosition().yProperty().removeListener(pivotY); + target.getPosition().zProperty().removeListener(pivotZ); + target.getHeading().angleProperty().removeListener(pivotHeading); + } + } + + /** + * Set camera to follow the selected subject + * @param subject to track + */ + private void trackSubject(Subject3D subject) { + untrackSubject(); + target = subject; + + updatePivot(target.getPosition()); + setYaw(target.getHeading().getAngle()); + + target.getPosition().xProperty().addListener(pivotX); + target.getPosition().yProperty().addListener(pivotY); + target.getPosition().zProperty().addListener(pivotZ); + target.getHeading().angleProperty().addListener(pivotHeading); + + this.setDistance(THIRD_PERSON_LIMIT); + this.setPitch(20); + } + public void setNearClip(double nearClip) { this.nearClip = nearClip; } @@ -110,10 +204,10 @@ public class View3D extends Pane { } /** - * Set object to centre on camera - * @param pivot centred object + * Sets the coordinates of the camera pivot once. + * @param pivot source of coordinates */ - public void setPivot(Subject3D pivot) { + public void updatePivot(Translate pivot) { this.pivot.setX(pivot.getX()); this.pivot.setY(pivot.getY()); this.pivot.setZ(pivot.getZ()); @@ -127,6 +221,27 @@ public class View3D extends Pane { this.distance.setZ(-distance); } + /** + * Adds delta to current distance and changes camera mode if applicable. + * Third person limit specifies the distance at which a third person camera + * switches to bird's-eye, remaining focused on the same position. + * @param delta amount to change distance by + */ + public void updateDistance(double delta) { + double distance = -this.distance.getZ() + delta; + + if(distance <= 0) { + this.setDistance(0); + } else if(distance > THIRD_PERSON_LIMIT) { + untrackSubject(); + this.setYaw(0); + this.setPitch(60); + this.setDistance(distance); + } else { + this.setDistance(distance); + } + } + /** * Set angle of camera from z-axis along ground * @param yaw in degrees diff --git a/racevisionGame/src/main/java/visualiser/utils/GPSConverter.java b/racevisionGame/src/main/java/visualiser/utils/GPSConverter.java new file mode 100644 index 00000000..22dd937f --- /dev/null +++ b/racevisionGame/src/main/java/visualiser/utils/GPSConverter.java @@ -0,0 +1,104 @@ +package visualiser.utils; + +import shared.dataInput.RaceDataSource; +import shared.model.GPSCoordinate; +import visualiser.model.GraphCoordinate; + +/** + * Converts GPS coordinates to view volume coordinates. Longitudes are equally spaced at all latitudes, + * which leads to inaccurate distance measurements close to the poles. This is acceptable as races are + * not likely to be set there. + */ +public class GPSConverter { + private double longRight; + private double longLeft; + private double latBottom; + private double latTop; + /** + * Conversion factor from longitude to view units + */ + private double longitudeFactor; + /** + * Conversion factor from latitude to view units + */ + private double latitudeFactor; + + /** + * Set up projection with default view boundaries from RaceDataSource + * @param source for view boundaries + * @param longitudeFactor separation of a degree of longitude in view units + * @param latitudeFactor separation of a degree of latitude in view units + */ + public GPSConverter(RaceDataSource source, double longitudeFactor, double latitudeFactor) { + this.latTop = source.getMapTopLeft().getLatitude(); + this.longLeft = source.getMapTopLeft().getLongitude(); + this.latBottom = source.getMapBottomRight().getLatitude(); + this.longRight = source.getMapBottomRight().getLongitude(); + this.longitudeFactor = longitudeFactor; + this.latitudeFactor = latitudeFactor; + } + + /** + * Converts GPS coordinates to coordinates for container. + * It is assumed that the provided GPSCoordinate will always be within the GPSCoordinate boundaries of the RaceMap. + * + * @param lat GPS latitude + * @param lon GPS longitude + * @return GraphCoordinate (pair of doubles) + * @see GraphCoordinate + */ + private GraphCoordinate convertGPS(double lat, double lon) { + + //Calculate the width/height, in gps coordinates, of the map. + double longWidth = longRight - longLeft; + double latHeight = latBottom - latTop; + + //Calculate the distance between the specified coordinate and the edge of the map. + double longDelta = lon - longLeft; + double latDelta = lat - latTop; + + //Calculate the proportion along horizontally, from the left, the coordinate should be. + double longProportion = longDelta / longWidth; + //Calculate the proportion along vertically, from the top, the coordinate should be. + double latProportion = latDelta / latHeight; + + //Check which metric dimension of our map is smaller. We use this to ensure that any rendered stuff retains its correct aspect ratio, and that everything is visible on screen. + double smallerDimension = Math.min(longitudeFactor, latitudeFactor); + + //Calculate the x and y pixel coordinates. + //We take the complement of latProportion to flip it. + int x = (int) (longProportion * smallerDimension); + int y = (int) (latProportion * smallerDimension); + + //Because we try to maintain the correct aspect ratio, we will end up with "spare" pixels along the larger dimension (e.g., width 800, height 600, 200 extra pixels along width). + double extraDistance = Math.abs(longitudeFactor - latitudeFactor); + //We therefore "center" the coordinates along this larger dimension, by adding half of the extra pixels. + if (longitudeFactor > latitudeFactor) { + x += extraDistance / 2; + } else { + y += extraDistance / 2; + } + + + //Finally, create the GraphCoordinate. + GraphCoordinate graphCoordinate = new GraphCoordinate(x, y); + + + return graphCoordinate; + + } + + /** + * Converts the GPS Coordinate to GraphCoordinate. + * It is assumed that the provided GPSCoordinate will always be within the GPSCoordinate boundaries of the RaceMap. + * + * @param coordinate GPSCoordinate representation of Latitude and Longitude. + * @return GraphCoordinate that the GPS is coordinates are to be displayed on the map. + * @see GraphCoordinate + * @see GPSCoordinate + */ + public GraphCoordinate convertGPS(GPSCoordinate coordinate) { + return convertGPS(coordinate.getLatitude(), coordinate.getLongitude()); + } + +} diff --git a/racevisionGame/src/main/resources/css/dayMode.css b/racevisionGame/src/main/resources/css/dayMode.css index aa14c68b..b62f8391 100644 --- a/racevisionGame/src/main/resources/css/dayMode.css +++ b/racevisionGame/src/main/resources/css/dayMode.css @@ -53,5 +53,39 @@ } #arrowImage { - -fx-image: url("/visualiser/images/arrow.png"); + -fx-graphic: url("/visualiser/images/arrow.png"); +} + +#nextButton { + -fx-background-image: url("/visualiser/images/ArrowRoundRight.png"); + -fx-background-size: 60px; + -fx-background-repeat: no-repeat; + -fx-background-position: center center; + -fx-focus-color: transparent; + -fx-background-color: transparent; +} +#nextButton:pressed { + -fx-background-image: url("/visualiser/images/ArrowRoundRightClicked.png"); + -fx-background-size: 60px; + -fx-background-repeat: no-repeat; + -fx-background-position: center center; + -fx-focus-color: transparent; + -fx-background-color: transparent; +} + +#previousButton { + -fx-background-image: url("/visualiser/images/ArrowRoundLeft.png"); + -fx-background-size: 60px; + -fx-background-repeat: no-repeat; + -fx-background-position: center center; + -fx-focus-color: transparent; + -fx-background-color: transparent; +} +#previousButton:pressed { + -fx-background-image: url("/visualiser/images/ArrowRoundLeftClicked.png"); + -fx-background-size: 60px; + -fx-background-repeat: no-repeat; + -fx-background-position: center center; + -fx-focus-color: transparent; + -fx-background-color: transparent; } diff --git a/racevisionGame/src/main/resources/css/nightMode.css b/racevisionGame/src/main/resources/css/nightMode.css index 7fe6a67b..deefa51a 100644 --- a/racevisionGame/src/main/resources/css/nightMode.css +++ b/racevisionGame/src/main/resources/css/nightMode.css @@ -57,3 +57,37 @@ #arrowImage { -fx-image: url("/visualiser/images/arrowLight.png"); } + +#nextButton { + -fx-background-image: url("/visualiser/images/ArrowRoundRight.png"); + -fx-background-size: 60px; + -fx-background-repeat: no-repeat; + -fx-background-position: center center; + -fx-focus-color: transparent; + -fx-background-color: transparent; +} +#nextButton:pressed { + -fx-background-image: url("/visualiser/images/ArrowRoundRightClicked.png"); + -fx-background-size: 60px; + -fx-background-repeat: no-repeat; + -fx-background-position: center center; + -fx-focus-color: transparent; + -fx-background-color: transparent; +} + +#previousButton { + -fx-background-image: url("/visualiser/images/ArrowRoundLeft.png"); + -fx-background-size: 60px; + -fx-background-repeat: no-repeat; + -fx-background-position: center center; + -fx-focus-color: transparent; + -fx-background-color: transparent; +} +#previousButton:pressed { + -fx-background-image: url("/visualiser/images/ArrowRoundLeftClicked.png"); + -fx-background-size: 60px; + -fx-background-repeat: no-repeat; + -fx-background-position: center center; + -fx-focus-color: transparent; + -fx-background-color: transparent; +} diff --git a/racevisionGame/src/main/resources/images/AC35_Racecourse_MAP.png b/racevisionGame/src/main/resources/images/AC35_Racecourse_MAP.png new file mode 100644 index 00000000..1b5e39ca Binary files /dev/null and b/racevisionGame/src/main/resources/images/AC35_Racecourse_MAP.png differ diff --git a/racevisionGame/src/main/resources/images/iMapLayout.png b/racevisionGame/src/main/resources/images/iMapLayout.png new file mode 100644 index 00000000..5932a60d Binary files /dev/null and b/racevisionGame/src/main/resources/images/iMapLayout.png differ diff --git a/racevisionGame/src/main/resources/images/mMapLayout.png b/racevisionGame/src/main/resources/images/mMapLayout.png new file mode 100644 index 00000000..fa0af51f Binary files /dev/null and b/racevisionGame/src/main/resources/images/mMapLayout.png differ diff --git a/racevisionGame/src/main/resources/images/oMapLayout.png b/racevisionGame/src/main/resources/images/oMapLayout.png new file mode 100644 index 00000000..ca520959 Binary files /dev/null and b/racevisionGame/src/main/resources/images/oMapLayout.png differ diff --git a/racevisionGame/src/main/resources/mock/mockXML/iMapLayout.xml b/racevisionGame/src/main/resources/mock/mockXML/iMapLayout.xml new file mode 100644 index 00000000..ca914b12 --- /dev/null +++ b/racevisionGame/src/main/resources/mock/mockXML/iMapLayout.xml @@ -0,0 +1,53 @@ + + + 5326 + FLEET + RACE_CREATION_TIME + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/racevisionGame/src/main/resources/mock/mockXML/mMapLayout.xml b/racevisionGame/src/main/resources/mock/mockXML/mMapLayout.xml new file mode 100644 index 00000000..ce715ba6 --- /dev/null +++ b/racevisionGame/src/main/resources/mock/mockXML/mMapLayout.xml @@ -0,0 +1,43 @@ + + + 5326 + FLEET + RACE_CREATION_TIME + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/racevisionGame/src/main/resources/mock/mockXML/oMapLayout.xml b/racevisionGame/src/main/resources/mock/mockXML/oMapLayout.xml new file mode 100644 index 00000000..5021bbba --- /dev/null +++ b/racevisionGame/src/main/resources/mock/mockXML/oMapLayout.xml @@ -0,0 +1,49 @@ + + + 5326 + FLEET + RACE_CREATION_TIME + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/racevisionGame/src/main/resources/mock/mockXML/raceTest.xml b/racevisionGame/src/main/resources/mock/mockXML/raceSixPlayers.xml similarity index 100% rename from racevisionGame/src/main/resources/mock/mockXML/raceTest.xml rename to racevisionGame/src/main/resources/mock/mockXML/raceSixPlayers.xml diff --git a/racevisionGame/src/main/resources/mock/mockXML/raceThreePlayers.xml b/racevisionGame/src/main/resources/mock/mockXML/raceThreePlayers.xml deleted file mode 100644 index e0b81837..00000000 --- a/racevisionGame/src/main/resources/mock/mockXML/raceThreePlayers.xml +++ /dev/null @@ -1,54 +0,0 @@ - - - 5326 - FLEET - RACE_CREATION_TIME - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/racevisionGame/src/main/resources/visualiser/images/ArrowRoundLeft.png b/racevisionGame/src/main/resources/visualiser/images/ArrowRoundLeft.png new file mode 100644 index 00000000..ea4f4d64 Binary files /dev/null and b/racevisionGame/src/main/resources/visualiser/images/ArrowRoundLeft.png differ diff --git a/racevisionGame/src/main/resources/visualiser/images/ArrowRoundLeftClicked.png b/racevisionGame/src/main/resources/visualiser/images/ArrowRoundLeftClicked.png new file mode 100644 index 00000000..794ba57c Binary files /dev/null and b/racevisionGame/src/main/resources/visualiser/images/ArrowRoundLeftClicked.png differ diff --git a/racevisionGame/src/main/resources/visualiser/images/ArrowRoundRight.png b/racevisionGame/src/main/resources/visualiser/images/ArrowRoundRight.png new file mode 100644 index 00000000..751698cb Binary files /dev/null and b/racevisionGame/src/main/resources/visualiser/images/ArrowRoundRight.png differ diff --git a/racevisionGame/src/main/resources/visualiser/images/ArrowRoundRightClicked.png b/racevisionGame/src/main/resources/visualiser/images/ArrowRoundRightClicked.png new file mode 100644 index 00000000..10c1b53b Binary files /dev/null and b/racevisionGame/src/main/resources/visualiser/images/ArrowRoundRightClicked.png differ diff --git a/racevisionGame/src/main/resources/visualiser/scenes/connect.fxml b/racevisionGame/src/main/resources/visualiser/scenes/connect.fxml deleted file mode 100644 index 56572741..00000000 --- a/racevisionGame/src/main/resources/visualiser/scenes/connect.fxml +++ /dev/null @@ -1,82 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/racevisionGame/src/main/resources/visualiser/scenes/raceView.fxml b/racevisionGame/src/main/resources/visualiser/scenes/raceView.fxml index c0de3323..b6496743 100644 --- a/racevisionGame/src/main/resources/visualiser/scenes/raceView.fxml +++ b/racevisionGame/src/main/resources/visualiser/scenes/raceView.fxml @@ -22,11 +22,7 @@ - +