Merge branch 'master' into network_on_VPS

main
Fan-Wu Yang 8 years ago
commit c91c4e5d7c

@ -219,7 +219,6 @@ public class Event {
long millisecondsToAdd = racePreStartTime + racePreparatoryTime; long millisecondsToAdd = racePreStartTime + racePreparatoryTime;
long secondsToAdd = millisecondsToAdd / 1000; long secondsToAdd = millisecondsToAdd / 1000;
DateTimeFormatter dateFormat = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ssZ"); DateTimeFormatter dateFormat = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ssZ");

@ -366,10 +366,8 @@ public class MockRace extends RaceState {
//Checks if the current boat has finished the race or not. //Checks if the current boat has finished the race or not.
boolean finish = this.isLastLeg(boat.getCurrentLeg()); boolean finish = this.isLastLeg(boat.getCurrentLeg());
if (!finish && totalElapsedMilliseconds >= updatePeriodMilliseconds) { if (!finish && totalElapsedMilliseconds >= updatePeriodMilliseconds && !boat.isColliding()) {
if(boat.isVelocityDefault()) setBoatSpeed(boat); if(boat.isVelocityDefault()) setBoatSpeed(boat);
//Calculates the distance travelled, in meters, in the current timeslice. //Calculates the distance travelled, in meters, in the current timeslice.
double distanceTravelledMeters = boat.calculateMetersTravelled(updatePeriodMilliseconds) * this.scaleFactor; double distanceTravelledMeters = boat.calculateMetersTravelled(updatePeriodMilliseconds) * this.scaleFactor;

@ -12,6 +12,7 @@ import network.Messages.Enums.RaceStatusEnum;
import network.Messages.LatestMessages; import network.Messages.LatestMessages;
import shared.model.RunnableWithFramePeriod; import shared.model.RunnableWithFramePeriod;
import java.util.ArrayList;
import java.util.Observable; import java.util.Observable;
import java.util.Observer; import java.util.Observer;
@ -90,7 +91,7 @@ public class RaceLogic implements RunnableWithFramePeriod, Observer {
server.parseSnapshot(); server.parseSnapshot();
waitForFramePeriod(previousFrameTime, currentTime, 50); waitForFramePeriod(previousFrameTime, currentTime, 16);
previousFrameTime = currentTime; previousFrameTime = currentTime;
} }
} }
@ -127,7 +128,7 @@ public class RaceLogic implements RunnableWithFramePeriod, Observer {
race.setBoatsStatusToRacing(); race.setBoatsStatusToRacing();
} }
waitForFramePeriod(previousFrameTime, currentTime, 50); waitForFramePeriod(previousFrameTime, currentTime, 16);
previousFrameTime = currentTime; previousFrameTime = currentTime;
} }
@ -139,6 +140,8 @@ public class RaceLogic implements RunnableWithFramePeriod, Observer {
*/ */
private void raceLoop() { private void raceLoop() {
ArrayList<MockBoat> collisionBoats = new ArrayList<>();
long previousFrameTime = System.currentTimeMillis(); long previousFrameTime = System.currentTimeMillis();
while (race.getRaceStatusEnum() != RaceStatusEnum.FINISHED && loopBool) { while (race.getRaceStatusEnum() != RaceStatusEnum.FINISHED && loopBool) {
@ -168,9 +171,17 @@ public class RaceLogic implements RunnableWithFramePeriod, Observer {
//If it is still racing, update its position. //If it is still racing, update its position.
if (boat.getStatus() == BoatStatusEnum.RACING) { if (boat.getStatus() == BoatStatusEnum.RACING) {
race.updatePosition(boat, framePeriod, race.getRaceClock().getDurationMilli()); race.updatePosition(boat, framePeriod, race.getRaceClock().getDurationMilli());
race.getColliderRegistry().rayCast(boat);
if(race.getColliderRegistry().rayCast(boat)){
//System.out.println("Collision!");
//Add boat to list
collisionBoats.add(boat);
}
//System.out.println(race.getColliderRegistry().rayCast(boat));
} }
} }
} else { } else {
@ -183,14 +194,19 @@ public class RaceLogic implements RunnableWithFramePeriod, Observer {
// Change wind direction // Change wind direction
race.changeWindDirection(); race.changeWindDirection();
//Pass collision boats in
server.parseBoatCollisions(collisionBoats);
//Parse the race snapshot. //Parse the race snapshot.
server.parseSnapshot(); server.parseSnapshot();
collisionBoats.clear();
//Update the last frame time. //Update the last frame time.
previousFrameTime = currentTime; previousFrameTime = currentTime;
} }
waitForFramePeriod(previousFrameTime, currentTime, 50); waitForFramePeriod(previousFrameTime, currentTime, 16);
previousFrameTime = currentTime; previousFrameTime = currentTime;
} }
} }

@ -3,6 +3,7 @@ package mock.model;
import network.AckSequencer; import network.AckSequencer;
import network.Messages.*; import network.Messages.*;
import network.Messages.Enums.BoatLocationDeviceEnum; import network.Messages.Enums.BoatLocationDeviceEnum;
import network.Messages.Enums.YachtEventEnum;
import network.Messages.Enums.XMLMessageType; import network.Messages.Enums.XMLMessageType;
import shared.model.Bearing; import shared.model.Bearing;
import shared.model.CompoundMark; import shared.model.CompoundMark;
@ -24,6 +25,7 @@ public class RaceServer {
private MockRace race; private MockRace race;
private LatestMessages latestMessages; private LatestMessages latestMessages;
private static RaceServer server; private static RaceServer server;
private List<YachtEvent> collisionEvents = new ArrayList<>();
/** /**
@ -75,9 +77,19 @@ public class RaceServer {
//Parse the race status. //Parse the race status.
snapshotMessages.add(parseRaceStatus()); snapshotMessages.add(parseRaceStatus());
//Parse collisions
if(collisionEvents.size()>0){
snapshotMessages.addAll(collisionEvents);
}
latestMessages.setSnapshot(snapshotMessages); latestMessages.setSnapshot(snapshotMessages);
updateXMLFiles(); updateXMLFiles();
//Reset collision list
collisionEvents.clear();
//System.out.println(collisionEvents.size());
} }
/** /**
@ -314,4 +326,28 @@ public class RaceServer {
return message; return message;
} }
/**
* Parse the yacht event and return it
* @param boat yacht with event
* @param event event that happened
* @return yacht event
*/
private YachtEvent parseYachtEvent(MockBoat boat, YachtEventEnum event){
YachtEvent yachtEvent = new YachtEvent(
System.currentTimeMillis(),
this.boatLocationSequenceNumber,
race.getRaceId(),
boat.getSourceID(),
1337,
event);
return yachtEvent;
}
public void parseBoatCollisions(ArrayList<MockBoat> boats){
for (MockBoat boat : boats){
collisionEvents.add(parseYachtEvent(boat, YachtEventEnum.COLLISION));
}
}
} }

@ -24,7 +24,8 @@ public abstract class Collider extends Observable implements Locatable {
// Direction of collider from heading // Direction of collider from heading
Bearing relative = Bearing.fromDegrees(absolute.degrees() - boat.getBearing().degrees()); Bearing relative = Bearing.fromDegrees(absolute.degrees() - boat.getBearing().degrees());
if(actualDistance <= distance) { if(!boat.isColliding() && actualDistance <= distance) {
boat.setColliding(true);
Collision collision = new Collision(boat, relative, distance); Collision collision = new Collision(boat, relative, distance);
// Notify object of collision // Notify object of collision
onCollisionEnter(collision); onCollisionEnter(collision);

@ -29,17 +29,18 @@ public class CollisionCommand extends ObserverCommand {
public void execute() { public void execute() {
this.azimuth = Azimuth.fromDegrees(boat.getBearing().degrees() - 180d); this.azimuth = Azimuth.fromDegrees(boat.getBearing().degrees() - 180d);
this.startingPosition = boat.getPosition(); this.startingPosition = boat.getPosition();
this.distance = 30; this.distance = 60;
boat.setVelocityDefault(false); boat.setVelocityDefault(false);
} }
@Override @Override
public void update(Observable o, Object arg) { public void update(Observable o, Object arg) {
if(GPSCoordinate.calculateDistanceMeters(boat.getPosition(), startingPosition) < distance) { if(GPSCoordinate.calculateDistanceMeters(boat.getPosition(), startingPosition) < distance) {
boat.setPosition(GPSCoordinate.calculateNewPosition(boat.getPosition(), 2, azimuth)); boat.setPosition(GPSCoordinate.calculateNewPosition(boat.getPosition(), 3, azimuth));
} else { } else {
race.deleteObserver(this); race.deleteObserver(this);
boat.setVelocityDefault(true); boat.setVelocityDefault(true);
boat.setColliding(false);
} }
} }
} }

@ -1,7 +1,7 @@
package mock.model.commandFactory; package mock.model.commandFactory;
import java.util.ArrayDeque;
import java.util.Queue; import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedDeque;
/** /**
* Wraps multiple commands into a composite to execute queued commands during a frame. * Wraps multiple commands into a composite to execute queued commands during a frame.
@ -10,15 +10,15 @@ public class CompositeCommand implements Command {
private Queue<Command> commands; private Queue<Command> commands;
public CompositeCommand() { public CompositeCommand() {
this.commands = new ArrayDeque<>(); this.commands = new ConcurrentLinkedDeque<>();
} }
public void addCommand(Command command) { public void addCommand(Command command) {
commands.add(command); commands.offer(command);
} }
@Override @Override
public void execute() { public void execute() {
while(!commands.isEmpty()) commands.remove().execute(); while(commands.peek() != null) commands.poll().execute();
} }
} }

@ -103,7 +103,6 @@ public class RaceXMLCreator {
} }
} }
/** /**
* Rotate the features in a race such as the boundary, and the marks. * Rotate the features in a race such as the boundary, and the marks.
* @param race the race to alter * @param race the race to alter

@ -98,6 +98,11 @@ public class Boat extends Collider {
*/ */
private boolean sailsOut = true; private boolean sailsOut = true;
/**
* Indicates whether boat is currently involved in a collision
*/
private boolean isColliding = false;
/** /**
* Constructs a boat object with a given sourceID, name, country/team abbreviation, and polars table. * Constructs a boat object with a given sourceID, name, country/team abbreviation, and polars table.
* *
@ -407,7 +412,7 @@ public class Boat extends Collider {
@Override @Override
public boolean rayCast(Boat boat) { public boolean rayCast(Boat boat) {
if(boat != this) { if(boat != this) {
return rayCast(boat, 100); return rayCast(boat, 15);
} else return false; } else return false;
} }
@ -419,4 +424,12 @@ public class Boat extends Collider {
notifyObservers(e); notifyObservers(e);
} }
} }
public boolean isColliding() {
return isColliding;
}
public void setColliding(boolean colliding) {
isColliding = colliding;
}
} }

@ -28,7 +28,7 @@ public class Constants {
* Frame periods are multiplied by this to get the amount of time a single frame represents. * Frame periods are multiplied by this to get the amount of time a single frame represents.
* E.g., frame period = 20ms, scale = 5, frame represents 20 * 5 = 100ms, and so boats are simulated for 100ms, even though only 20ms actually occurred. * E.g., frame period = 20ms, scale = 5, frame represents 20 * 5 = 100ms, and so boats are simulated for 100ms, even though only 20ms actually occurred.
*/ */
public static final int RaceTimeScale = 10; public static final int RaceTimeScale = 2;
/** /**
* The race pre-start time, in milliseconds. 30 seconds. * The race pre-start time, in milliseconds. 30 seconds.

@ -29,11 +29,6 @@ public class Mark extends Collider{
*/ */
private GPSCoordinate position; private GPSCoordinate position;
/**
* Repulsion radius of the mark
*/
private double repulsionRadius = 50;
/** /**
* Constructs a mark with a given source ID, name, and position. * Constructs a mark with a given source ID, name, and position.
* @param sourceID The source ID of the mark. * @param sourceID The source ID of the mark.
@ -97,7 +92,7 @@ public class Mark extends Collider{
@Override @Override
public boolean rayCast(Boat boat) { public boolean rayCast(Boat boat) {
return rayCast(boat, repulsionRadius); return rayCast(boat, 15);
} }
@Override @Override

@ -0,0 +1,46 @@
package visualiser.Commands.VisualiserRaceCommands;
import javafx.scene.media.AudioClip;
import mock.model.commandFactory.Command;
import network.Messages.YachtEvent;
import shared.exceptions.BoatNotFoundException;
import visualiser.model.VisualiserBoat;
import visualiser.model.VisualiserRaceState;
/**
* Created by zwu18 on 4/09/17.
*/
public class BoatCollisionCommand implements Command {
YachtEvent yachtEvent;
VisualiserRaceState visualiserRace;
public BoatCollisionCommand(YachtEvent yachtEvent, VisualiserRaceState visualiserRace){
this.yachtEvent = yachtEvent;
this.visualiserRace = visualiserRace;
}
@Override
public void execute() {
if(visualiserRace.getPlayerBoatID()==yachtEvent.getSourceID()){
//System.out.println("I crashed!");
AudioClip sound = new AudioClip(this.getClass().getResource("/visualiser/sounds/collision.wav").toExternalForm());
sound.play();
} else {
//System.out.println("Someone else crashed!");
AudioClip sound = new AudioClip(this.getClass().getResource("/visualiser/sounds/quietcollision.wav").toExternalForm());
sound.play();
}
try {
VisualiserBoat boat = visualiserRace.getBoat(yachtEvent.getSourceID());
boat.setHasCollided(true);
} catch (BoatNotFoundException e) {
e.printStackTrace();
}
}
}

@ -1,5 +1,6 @@
package visualiser.Commands.VisualiserRaceCommands; package visualiser.Commands.VisualiserRaceCommands;
import javafx.scene.media.AudioClip;
import mock.model.commandFactory.Command; import mock.model.commandFactory.Command;
import network.Messages.BoatStatus; import network.Messages.BoatStatus;
import network.Messages.Enums.BoatStatusEnum; import network.Messages.Enums.BoatStatusEnum;
@ -176,6 +177,10 @@ public class RaceStatusCommand implements Command {
//Record order in which boat finished leg. //Record order in which boat finished leg.
visualiserRace.getLegCompletionOrder().get(boat.getCurrentLeg()).add(boat); visualiserRace.getLegCompletionOrder().get(boat.getCurrentLeg()).add(boat);
//play sound
AudioClip sound = new AudioClip(getClass().getResource("/visualiser/sounds/passmark.wav").toExternalForm());
sound.play();
//Update boat. //Update boat.
boat.setCurrentLeg(leg); boat.setCurrentLeg(leg);
boat.setTimeAtLastMark(visualiserRace.getRaceClock().getCurrentTime()); boat.setTimeAtLastMark(visualiserRace.getRaceClock().getCurrentTime());

@ -23,7 +23,9 @@ public class VisualiserRaceCommandFactory {
switch (message.getType()) { switch (message.getType()) {
case BOATLOCATION: return new BoatLocationCommand((BoatLocation) message, visualiserRace); case BOATLOCATION:
//System.out.println("Boat location received");
return new BoatLocationCommand((BoatLocation) message, visualiserRace);
case RACESTATUS: return new RaceStatusCommand((RaceStatus) message, visualiserRace); case RACESTATUS: return new RaceStatusCommand((RaceStatus) message, visualiserRace);
@ -31,6 +33,11 @@ public class VisualiserRaceCommandFactory {
case ASSIGN_PLAYER_BOAT: return new AssignPlayerBoatCommand((AssignPlayerBoat) message, visualiserRace); case ASSIGN_PLAYER_BOAT: return new AssignPlayerBoatCommand((AssignPlayerBoat) message, visualiserRace);
case YACHTEVENTCODE:
return new BoatCollisionCommand((YachtEvent) message, visualiserRace);
default: throw new CommandConstructionException("Could not create VisualiserRaceCommand. Unrecognised or unsupported MessageType: " + message.getType()); default: throw new CommandConstructionException("Could not create VisualiserRaceCommand. Unrecognised or unsupported MessageType: " + message.getType());
} }

@ -15,6 +15,7 @@ import javafx.scene.control.Label;
import javafx.scene.image.ImageView; import javafx.scene.image.ImageView;
import javafx.scene.layout.AnchorPane; import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.GridPane; import javafx.scene.layout.GridPane;
import javafx.scene.paint.PhongMaterial;
import javafx.scene.shape.MeshView; import javafx.scene.shape.MeshView;
import mock.app.Event; import mock.app.Event;
import network.Messages.Enums.RaceStatusEnum; import network.Messages.Enums.RaceStatusEnum;
@ -138,11 +139,15 @@ public class InGameLobbyController extends Controller {
playerContainer.add(playerBoatToSet, (count % 3) , row); playerContainer.add(playerBoatToSet, (count % 3) , row);
playerContainer.setMargin(playerBoatToSet, new Insets(10, 10, 10, 10)); playerContainer.setMargin(playerBoatToSet, new Insets(10, 10, 10, 10));
SeaSurface sea = new SeaSurface(750, 200, 250, 0, 210); SeaSurface sea = new SeaSurface(750, 200);
subjects.add(sea.getSurface()); sea.setX(250);
sea.setZ(210);
subjects.add(sea);
MeshView mesh = new MeshView(importer.getImport()); MeshView mesh = new MeshView(importer.getImport());
Subject3D subject = new Subject3D(mesh); PhongMaterial boatColorMat = new PhongMaterial(boat.getColor());
mesh.setMaterial(boatColorMat);
Subject3D subject = new Subject3D(mesh,0);
subjects.add(subject); subjects.add(subject);
playerBoatToSet.setDistance(50); playerBoatToSet.setDistance(50);
@ -159,7 +164,7 @@ public class InGameLobbyController extends Controller {
}; };
rotate.start(); rotate.start();
allPlayerLabels.get(count).setText("Player: " + boat.getSourceID()); allPlayerLabels.get(count).setText(boat.getName());
allPlayerLabels.get(count).toFront(); allPlayerLabels.get(count).toFront();
count += 1; count += 1;
if (count > 2){ if (count > 2){
@ -250,7 +255,7 @@ public class InGameLobbyController extends Controller {
visualiserRaceEvent.getVisualiserRaceState().getBoats().removeListener(lobbyUpdateListener); visualiserRaceEvent.getVisualiserRaceState().getBoats().removeListener(lobbyUpdateListener);
RaceViewController rvc = (RaceViewController) RaceViewController rvc = (RaceViewController)
loadScene("raceView.fxml"); loadScene("newRaceView.fxml");
rvc.startRace(visualiserRaceEvent, controllerClient, rvc.startRace(visualiserRaceEvent, controllerClient,
isHost); isHost);
} catch (IOException e) { } catch (IOException e) {

@ -7,6 +7,8 @@ import javafx.scene.control.Button;
import javafx.scene.control.TableColumn; import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView; import javafx.scene.control.TableView;
import javafx.scene.control.TextField; import javafx.scene.control.TextField;
import javafx.scene.layout.AnchorPane;
import javafx.scene.media.AudioClip;
import network.Messages.HostGame; import network.Messages.HostGame;
import visualiser.app.MatchBrowserSingleton; import visualiser.app.MatchBrowserSingleton;
import visualiser.model.RaceConnection; import visualiser.model.RaceConnection;
@ -34,6 +36,8 @@ public class LobbyController extends Controller {
private ObservableList<RaceConnection> connections; private ObservableList<RaceConnection> connections;
private ObservableList<RaceConnection> customConnections; private ObservableList<RaceConnection> customConnections;
private AudioClip sound;
//the socket for match browser //the socket for match browser
private DatagramSocket udpSocket; private DatagramSocket udpSocket;
private MatchBrowserLobbyInterface matchBrowserLobbyInterface; private MatchBrowserLobbyInterface matchBrowserLobbyInterface;
@ -67,6 +71,8 @@ public class LobbyController extends Controller {
* Refreshes the connections in the lobby * Refreshes the connections in the lobby
*/ */
public void refreshBtnPressed(){ public void refreshBtnPressed(){
sound = new AudioClip(this.getClass().getResource("/visualiser/sounds/buttonpress.wav").toExternalForm());
sound.play();
addServerGames(); addServerGames();
for(RaceConnection connection: connections) { for(RaceConnection connection: connections) {
connection.check(); connection.check();
@ -92,6 +98,8 @@ public class LobbyController extends Controller {
} }
public void menuBtnPressed() throws IOException { public void menuBtnPressed() throws IOException {
sound = new AudioClip(this.getClass().getResource("/visualiser/sounds/buttonpress.wav").toExternalForm());
sound.play();
matchBrowserLobbyInterface.closeSocket(); matchBrowserLobbyInterface.closeSocket();
loadScene("title.fxml"); loadScene("title.fxml");
} }
@ -100,6 +108,8 @@ public class LobbyController extends Controller {
* adds a new connection * adds a new connection
*/ */
public void addConnectionPressed(){ public void addConnectionPressed(){
sound = new AudioClip(this.getClass().getResource("/visualiser/sounds/buttonpress.wav").toExternalForm());
sound.play();
String hostName = addressFld.getText(); String hostName = addressFld.getText();
String portString = portFld.getText(); String portString = portFld.getText();
try { try {

@ -8,27 +8,32 @@ import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList; import javafx.collections.ObservableList;
import javafx.collections.transformation.SortedList; import javafx.collections.transformation.SortedList;
import javafx.fxml.FXML; import javafx.fxml.FXML;
import javafx.scene.AmbientLight;
import javafx.scene.PointLight;
import javafx.scene.chart.LineChart; import javafx.scene.chart.LineChart;
import javafx.scene.control.*; import javafx.scene.control.*;
import javafx.scene.input.KeyCode; import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent; import javafx.scene.input.KeyEvent;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.GridPane; import javafx.scene.layout.GridPane;
import javafx.scene.layout.StackPane; import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.scene.paint.Material;
import javafx.scene.paint.PhongMaterial;
import javafx.scene.shape.MeshView; import javafx.scene.shape.MeshView;
import javafx.scene.shape.Sphere; import javafx.scene.shape.Shape3D;
import javafx.scene.transform.Translate; import javafx.scene.transform.Translate;
import javafx.util.Callback; import javafx.util.Callback;
import network.Messages.Enums.RaceStatusEnum; import network.Messages.Enums.RaceStatusEnum;
import shared.dataInput.RaceDataSource; import shared.dataInput.RaceDataSource;
import shared.model.Leg; import shared.exceptions.BoatNotFoundException;
import shared.model.Mark; import shared.model.*;
import visualiser.app.App; import visualiser.app.App;
import visualiser.enums.TutorialState; import visualiser.enums.TutorialState;
import visualiser.gameController.ControllerClient; import visualiser.gameController.ControllerClient;
import visualiser.gameController.Keys.ControlKey; import visualiser.gameController.Keys.ControlKey;
import visualiser.gameController.Keys.KeyFactory; import visualiser.gameController.Keys.KeyFactory;
import visualiser.layout.Subject3D; import visualiser.layout.*;
import visualiser.layout.View3D;
import visualiser.model.Sparkline; import visualiser.model.Sparkline;
import visualiser.model.VisualiserBoat; import visualiser.model.VisualiserBoat;
import visualiser.model.VisualiserRaceEvent; import visualiser.model.VisualiserRaceEvent;
@ -61,6 +66,15 @@ public class RaceViewController extends Controller {
private View3D view3D; private View3D view3D;
private ObservableList<Subject3D> viewSubjects; private ObservableList<Subject3D> viewSubjects;
/**
* Arrow pointing to next mark in third person
*/
private Subject3D nextMarkArrow;
/**
* Animation loop for rotating mark arrow
*/
private AnimationTimer pointToMark;
// note: it says it's not used but it is! do not remove :) // note: it says it's not used but it is! do not remove :)
private @FXML ArrowController arrowController; private @FXML ArrowController arrowController;
private @FXML GridPane canvasBase; private @FXML GridPane canvasBase;
@ -77,6 +91,8 @@ public class RaceViewController extends Controller {
private @FXML TableColumn<VisualiserBoat, Number> boatSpeedColumn; private @FXML TableColumn<VisualiserBoat, Number> boatSpeedColumn;
private @FXML LineChart<Number, Number> sparklineChart; private @FXML LineChart<Number, Number> sparklineChart;
private @FXML Label tutorialText; private @FXML Label tutorialText;
private @FXML AnchorPane infoWrapper;
private @FXML AnchorPane lineChartWrapper;
/** /**
* Displays a specified race. * Displays a specified race.
@ -121,9 +137,8 @@ public class RaceViewController extends Controller {
} }
} }
/** private AnimationTimer arrowToNextMark;
* Sets up the listener and actions that occur when a key is pressed.
*/
private void initKeypressHandler() { private void initKeypressHandler() {
racePane.addEventFilter(KeyEvent.KEY_PRESSED, event -> { racePane.addEventFilter(KeyEvent.KEY_PRESSED, event -> {
String codeString = event.getCode().toString(); String codeString = event.getCode().toString();
@ -189,10 +204,23 @@ public class RaceViewController extends Controller {
* Initialises the various UI components to listen to the {@link #visualiserRace}. * Initialises the various UI components to listen to the {@link #visualiserRace}.
*/ */
private void initialiseRaceVisuals() { private void initialiseRaceVisuals() {
// Import arrow mesh
URL asset = this.getClass().getClassLoader().getResource("assets/arrow V1.0.4.stl");
StlMeshImporter importer = new StlMeshImporter();
importer.read(asset);
MeshView arrow = new MeshView(importer.getImport());
PhongMaterial arrowMat = new PhongMaterial(Color.RED);
arrow.setMaterial(arrowMat);
this.nextMarkArrow = new Annotation3D(arrow);
this.nextMarkArrow.setScale(0.1);
// initialise displays // initialise displays
initialiseFps(); initialiseFps();
initialiseInfoTable(); initialiseInfoTable();
initialiseView3D(); initialiseView3D(this.visualiserRace);
initialiseRaceClock(); initialiseRaceClock();
raceTimer(); // start the timer raceTimer(); // start the timer
new Sparkline(this.raceState, this.sparklineChart); new Sparkline(this.raceState, this.sparklineChart);
@ -200,55 +228,124 @@ public class RaceViewController extends Controller {
arrowController.setWindProperty(this.raceState.windProperty()); arrowController.setWindProperty(this.raceState.windProperty());
} }
private void initialiseView3D() { private void initialiseView3D(VisualiserRaceEvent race) {
viewSubjects = FXCollections.observableArrayList(); viewSubjects = FXCollections.observableArrayList();
AmbientLight ambientLight = new AmbientLight(Color.web("#CCCCFF"));
ambientLight.setTranslateX(250);
ambientLight.setTranslateZ(210);
ambientLight.setLightOn(true);
PointLight pointLight = new PointLight(Color.web("#AAAAFF"));
pointLight.setTranslateX(250);
pointLight.setTranslateZ(210);
pointLight.setLightOn(true);
// Import boat mesh // Import boat mesh
URL asset = RaceViewController.class.getClassLoader().getResource("assets/V1.2 " + URL asset = RaceViewController.class.getClassLoader().getResource("assets/V1.2 Complete Boat.stl");
"Complete Boat.stl");
StlMeshImporter importer = new StlMeshImporter(); StlMeshImporter importer = new StlMeshImporter();
importer.read(asset); importer.read(asset);
// Configure camera angles and control // Configure camera angles and control
view3D = new View3D(); URL markerAsset = RaceViewController.class.getClassLoader().getResource("assets/Bouy V1.1.stl");
StlMeshImporter importerMark = new StlMeshImporter();
importerMark.read(markerAsset);
URL alternateBoatAsset = RaceViewController.class.getClassLoader().getResource("assets/V1.3 BurgerBoat.stl");
StlMeshImporter importerBurgerBoat = new StlMeshImporter();
importerBurgerBoat.read(alternateBoatAsset);
view3D = new View3D(false);
view3D.setItems(viewSubjects);
view3D.setDistance(1050); view3D.setDistance(1050);
view3D.setYaw(0); view3D.setBirdsEye();
view3D.setPitch(60);
view3D.enableTracking(); view3D.enableTracking();
view3D.addAmbientLight(ambientLight);
view3D.addPointLight(pointLight);
canvasBase.add(view3D, 0, 0); canvasBase.add(view3D, 0, 0);
// Set up projection from GPS to view // Set up projection from GPS to view
RaceDataSource raceData = raceState.getRaceDataSource(); RaceDataSource raceData = visualiserRace.getVisualiserRaceState().getRaceDataSource();
final GPSConverter gpsConverter = new GPSConverter(raceData, 450, 450); final GPSConverter gpsConverter = new GPSConverter(raceData, 450, 450);
view3D.setItems(viewSubjects); SkyBox skyBox = new SkyBox(750, 200, 250, 0, 210);
viewSubjects.addAll(skyBox.getSkyBoxPlanes());
// Set up sea surface
SeaSurface sea = new SeaSurface(750, 200);
sea.setX(250);
sea.setZ(210);
viewSubjects.add(sea);
// Set up sea surface overlay
SeaSurface seaOverlay = new SeaSurface(4000, 200);
seaOverlay.setX(250);
seaOverlay.setZ(210);
viewSubjects.add(seaOverlay);
Boundary3D boundary3D = new Boundary3D(visualiserRace.getVisualiserRaceState().getRaceDataSource().getBoundary(), gpsConverter);
for (Subject3D subject3D: boundary3D.getBoundaryNodes()){
viewSubjects.add(subject3D);
}
// Position and add each mark to view // Position and add each mark to view
for (Mark mark : raceState.getMarks()) { for(Mark mark: race.getVisualiserRaceState().getMarks()) {
Subject3D subject = new Subject3D(new Sphere(2)); MeshView mesh = new MeshView(importerMark.getImport());
subject.setX(gpsConverter.convertGPS(mark.getPosition()).getX()); Subject3D markModel = new Subject3D(mesh, mark.getSourceID());
subject.setZ(gpsConverter.convertGPS(mark.getPosition()).getY());
markModel.setX(gpsConverter.convertGPS(mark.getPosition()).getX());
markModel.setZ(gpsConverter.convertGPS(mark.getPosition()).getY());
viewSubjects.add(subject); viewSubjects.add(markModel);
} }
// Position and add each boat to view // Position and add each boat to view
for (VisualiserBoat boat : raceState.getBoats()) { for(VisualiserBoat boat: race.getVisualiserRaceState().getBoats()) {
MeshView mesh = new MeshView(importer.getImport()); MeshView mesh;
Subject3D subject = new Subject3D(mesh); if(boat.getSourceID() == race.getVisualiserRaceState().getPlayerBoatID()) {
viewSubjects.add(subject); mesh = new MeshView(importer.getImport());
} else {
mesh = new MeshView(importerBurgerBoat.getImport());
}
PhongMaterial boatColorMat = new PhongMaterial(boat.getColor());
mesh.setMaterial(boatColorMat);
Subject3D boatModel = new Subject3D(mesh, boat.getSourceID());
viewSubjects.add(boatModel);
// Track this boat's movement with the new subject // Track this boat's movement with the new subject
AnimationTimer trackBoat = new AnimationTimer() { AnimationTimer trackBoat = new AnimationTimer() {
@Override public void handle(long now) { @Override
subject.setHeading(boat.getBearing().degrees()); public void handle(long now) {
subject.setX(gpsConverter.convertGPS(boat.getPosition()).getX()); boatModel.setHeading(boat.getBearing().degrees());
subject.setZ(gpsConverter.convertGPS(boat.getPosition()).getY()); boatModel.setX(gpsConverter.convertGPS(boat.getPosition()).getX());
boatModel.setZ(gpsConverter.convertGPS(boat.getPosition()).getY());
} }
}; };
trackBoat.start(); trackBoat.start();
Material markColor = new PhongMaterial(new Color(0.15,0.9,0.2,1));
CompoundMark nextMark = boat.getCurrentLeg().getEndCompoundMark();
view3D.getShape(nextMark.getMark1().getSourceID()).getMesh().setMaterial(markColor);
if(nextMark.getMark2() != null) {
view3D.getShape(nextMark.getMark2().getSourceID()).getMesh().setMaterial(markColor);
}
Subject3D shockwave = new Shockwave(10);
viewSubjects.add(shockwave);
boat.legProperty().addListener((o, prev, curr) -> Platform.runLater(() -> swapColours(curr)));
boat.hasCollidedProperty().addListener((o, prev, curr) -> Platform.runLater(() -> showCollision(boat, shockwave)));
} }
// Fix initial bird's-eye position // Fix initial bird's-eye position
view3D.updatePivot(new Translate(250, 0, 210)); view3D.updatePivot(new Translate(250, 0, 210));
view3D.targetProperty().addListener((o, prev, curr)-> {
if(curr != null && visualiserRace.getVisualiserRaceState().isVisualiserBoat(curr.getSourceID())) {
addThirdPersonAnnotations(curr);
} else {
removeThirdPersonAnnotations();
}
});
// Bind zooming to scrolling // Bind zooming to scrolling
view3D.setOnScroll(e -> { view3D.setOnScroll(e -> {
view3D.updateDistance(e.getDeltaY()); view3D.updateDistance(e.getDeltaY());
@ -257,7 +354,7 @@ public class RaceViewController extends Controller {
// Bind zooming to keypress (Z/X default) // Bind zooming to keypress (Z/X default)
racePane.addEventFilter(KeyEvent.KEY_PRESSED, e -> { racePane.addEventFilter(KeyEvent.KEY_PRESSED, e -> {
ControlKey key = keyFactory.getKey(e.getCode().toString()); ControlKey key = keyFactory.getKey(e.getCode().toString());
if (key != null) { if(key != null) {
switch (key.toString()) { switch (key.toString()) {
case "Zoom In": case "Zoom In":
//Check if race is a tutorial //Check if race is a tutorial
@ -294,6 +391,95 @@ public class RaceViewController extends Controller {
}); });
} }
private void showCollision(VisualiserBoat boat, Subject3D shockwave) {
Subject3D boatModel = view3D.getShape(boat.getSourceID());
AnimationTimer shockFront = new AnimationTimer() {
double opacity = 1;
@Override
public void handle(long now) {
shockwave.setX(boatModel.getPosition().getX());
shockwave.setY(boatModel.getPosition().getY());
shockwave.setZ(boatModel.getPosition().getZ());
if(opacity <= 0) {
shockwave.getMesh().setMaterial(new PhongMaterial(Color.TRANSPARENT));
boat.setHasCollided(false);
this.stop();
}
else {
shockwave.getMesh().setMaterial(new PhongMaterial(new Color(1,0,0,opacity)));
opacity -= 0.1;
}
}
};
shockFront.start();
}
private void addThirdPersonAnnotations(Subject3D subject3D) {
viewSubjects.add(nextMarkArrow);
final VisualiserBoat boat;
try {
boat = visualiserRace.getVisualiserRaceState().getBoat(subject3D.getSourceID());
} catch (BoatNotFoundException e) {
e.printStackTrace();
return;
}
arrowToNextMark = new AnimationTimer() {
@Override
public void handle(long now) {
CompoundMark target = boat.getCurrentLeg().getEndCompoundMark();
Bearing headingToMark = GPSCoordinate.calculateBearing(boat.getPosition(), target.getAverageGPSCoordinate());
nextMarkArrow.setX(view3D.getPivot().getX());
nextMarkArrow.setY(view3D.getPivot().getY());
nextMarkArrow.setZ(view3D.getPivot().getZ() + 15);
nextMarkArrow.setHeading(headingToMark.degrees());
}
};
arrowToNextMark.start();
}
private void removeThirdPersonAnnotations() {
viewSubjects.remove(nextMarkArrow);
if (arrowToNextMark != null) {
arrowToNextMark.stop();
}
}
/**
* Swap the colour of the next mark to pass with the last mark passed
* @param leg boat has started on
*/
private void swapColours(Leg leg) {
CompoundMark start = leg.getStartCompoundMark();
CompoundMark end = leg.getEndCompoundMark();
//The last leg "finish" doesn't have compound marks.
if (start == null || end == null ) {
return;
}
Shape3D start1 = view3D.getShape(start.getMark1().getSourceID()).getMesh();
Shape3D end1 = view3D.getShape(end.getMark1().getSourceID()).getMesh();
Material nextMark = start1.getMaterial();
Material lastMark = end1.getMaterial();
start1.setMaterial(lastMark);
if(start.getMark2() != null) {
Shape3D start2 = view3D.getShape(start.getMark2().getSourceID()).getMesh();
start2.setMaterial(lastMark);
}
end1.setMaterial(nextMark);
if(end.getMark2() != null) {
Shape3D end2 = view3D.getShape(end.getMark2().getSourceID()).getMesh();
end2.setMaterial(nextMark);
}
}
/** /**
* Initialises the frame rate functionality. This allows for toggling the * Initialises the frame rate functionality. This allows for toggling the
* frame rate, and connect the fps label to the race's fps property. * frame rate, and connect the fps label to the race's fps property.
@ -398,6 +584,7 @@ public class RaceViewController extends Controller {
/** /**
* Transition from the race view to the finish view. * Transition from the race view to the finish view.
* @throws IOException Thrown if the finish scene cannot be loaded.
*/ */
private void finishRace() throws IOException { private void finishRace() throws IOException {
RaceFinishController fc = RaceFinishController fc =
@ -442,23 +629,7 @@ public class RaceViewController extends Controller {
* toggles if the info table is shown * toggles if the info table is shown
*/ */
private void toggleTable() { private void toggleTable() {
double tablePercent = 1 - (boatPlacingColumn.getPrefWidth() + infoWrapper.setVisible(infoTableShow);
boatTeamColumn.getPrefWidth() + boatMarkColumn.getPrefWidth()
+ boatSpeedColumn.getPrefWidth())/racePane.getWidth();
if (infoTableShow) {
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 {
racePane.setDividerPositions(1);
arrowPane.setScaleX(1);
arrowPane.setScaleY(1);
arrowPane.setTranslateX(0);
arrowPane.setTranslateY(0);
}
boatInfoTable.refresh(); boatInfoTable.refresh();
infoTableShow = !infoTableShow; infoTableShow = !infoTableShow;
} }
@ -564,4 +735,4 @@ public class RaceViewController extends Controller {
} }
} }
} }

@ -6,11 +6,20 @@ import javafx.scene.control.RadioButton;
import javafx.scene.image.Image; import javafx.scene.image.Image;
import javafx.scene.image.ImageView; import javafx.scene.image.ImageView;
import javafx.scene.layout.Pane; import javafx.scene.layout.Pane;
import javafx.scene.media.AudioClip;
import javafx.scene.media.Media;
import javafx.scene.media.MediaPlayer;
import javafx.stage.Modality; import javafx.stage.Modality;
import mock.exceptions.EventConstructionException; import mock.exceptions.EventConstructionException;
import visualiser.app.App; import visualiser.app.App;
import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.ResourceBundle;
import java.util.logging.Level;
import java.util.logging.Logger;
/** /**
* Controller for the opening title window. * Controller for the opening title window.

@ -80,8 +80,9 @@ public class ControllerServer implements RunnableWithFramePeriod {
try { try {
Command command = CommandFactory.createCommand(raceState, boatAction); Command command = CommandFactory.createCommand(raceState, boatAction);
compositeCommand.addCommand(command); if(command != null) {
compositeCommand.addCommand(command);
}
} catch (CommandConstructionException e) { } catch (CommandConstructionException e) {
Logger.getGlobal().log(Level.WARNING, "ControllerServer could not create a Command for BoatAction: " + boatAction + ".", e); Logger.getGlobal().log(Level.WARNING, "ControllerServer could not create a Command for BoatAction: " + boatAction + ".", e);

@ -0,0 +1,24 @@
package visualiser.layout;
import javafx.scene.shape.Shape3D;
/**
* Created by connortaylorbrown on 13/09/17.
*/
public class Annotation3D extends Subject3D {
/**
* Constructor for view subject wrapper
*
* @param mesh to be rendered
*/
public Annotation3D(Shape3D mesh) {
super(mesh, 0);
}
/**
* Prevent rescaling of this subject
* @param scale ignored
*/
@Override
public void setScale(double scale) {}
}

@ -0,0 +1,89 @@
package visualiser.layout;
import com.sun.corba.se.impl.orbutil.graph.Graph;
import javafx.scene.shape.Box;
import javafx.scene.shape.MeshView;
import javafx.scene.shape.Rectangle;
import javafx.scene.shape.Sphere;
import shared.model.GPSCoordinate;
import visualiser.model.GraphCoordinate;
import visualiser.utils.GPSConverter;
import java.util.ArrayList;
import java.util.List;
/**
* Class that creates a 3d boundary based on gps coordinates
*/
public class Boundary3D {
public static double thickness = 1;
private List<Subject3D> boundaryNodes = new ArrayList<>();
private List<Subject3D> boundaryConnectors = new ArrayList<>();
private GPSConverter gpsConverter;
public Boundary3D(List<GPSCoordinate> points, GPSConverter gpsConverter){
this.gpsConverter = gpsConverter;
setBoundaryNodes(points);
}
/**
* Splits up the list so that it generates a edge of the boundary
* @param points boundary gpscoordinate
*/
private void setBoundaryNodes(List<GPSCoordinate> points){
if (points.size() < 2){
return;
}
for (int i = 0; i < points.size(); i++){
if (i + 1 != points.size()){
addBound(points.get(i), points.get(i + 1));
} else {
addBound(points.get(i), points.get(0));
}
}
}
/**
* Add a two point boundary this will create a sphere at coord1 and a line to coord 2
* this is to reduce double up (2 spheres in one point).
* @param coord1 point to make sphere and start the line.
* @param coord2 point to end the line.
*/
private void addBound(GPSCoordinate coord1, GPSCoordinate coord2){
GraphCoordinate graphCoord1 = gpsConverter.convertGPS(coord1);
GraphCoordinate graphCoord2 = gpsConverter.convertGPS(coord2);
GraphCoordinate avgCoord = new GraphCoordinate((graphCoord1.getX() + graphCoord2.getX()) / 2,
(graphCoord1.getY() + graphCoord2.getY()) / 2);
double a = (graphCoord1.getX() - graphCoord2.getX());
double b = (graphCoord1.getY() - graphCoord2.getY());
double c = Math.sqrt(a * a + b * b);
Subject3D bound1 = new Annotation3D(new Sphere(thickness * 2));
bound1.setX(graphCoord1.getX());
bound1.setZ(graphCoord1.getY());
boundaryNodes.add(bound1);
Subject3D connector = new Annotation3D(new Box(c, thickness, thickness));
connector.setX(avgCoord.getX());
connector.setZ(avgCoord.getY());
double angle = 90 + Math.toDegrees(GPSConverter.getAngle(graphCoord2, graphCoord1));
connector.setHeading(angle);
boundaryConnectors.add(connector);
}
/**
* get the 3d objects to draw
* @return 3d boundary to draw
*/
public List<Subject3D> getBoundaryNodes(){
//these two must be concatenated with nodes after connectors else the spheres will not overlap the lines
ArrayList<Subject3D> result = new ArrayList<>(boundaryConnectors);
result.addAll(boundaryNodes);
return result;
}
}

@ -1,5 +1,13 @@
package visualiser.layout; package visualiser.layout;
import com.sun.javafx.geom.PickRay;
import com.sun.javafx.scene.input.PickResultChooser;
import com.sun.javafx.sg.prism.NGNode;
import javafx.scene.Node;
import javafx.scene.shape.*;
import java.util.ArrayList;
import java.util.Arrays;
import javafx.scene.shape.TriangleMesh; import javafx.scene.shape.TriangleMesh;
import java.util.ArrayList; import java.util.ArrayList;
@ -29,13 +37,13 @@ public class Plane3D extends TriangleMesh{
for (float l = 0; l <= length; l += subLength) { for (float l = 0; l <= length; l += subLength) {
for (float w = 0; w <= width; w += subWidth){ for (float w = 0; w <= width; w += subWidth){
//add points //add points
pointsList.add(w + startW); pointsList.add(w + startW);
pointsList.add(l + startL); pointsList.add(l + startL);
pointsList.add(0f); pointsList.add(0f);
//addTexture coords //addTexture coords
textureCoord.add(1 - w/width); textureCoord.add(1 - w/width);
textureCoord.add(1 - l/length); textureCoord.add(1 - l/length);
} }
} }
@ -92,6 +100,7 @@ public class Plane3D extends TriangleMesh{
float x = this.getPoints().get(i); float x = this.getPoints().get(i);
float y = this.getPoints().get(i + 1); float y = this.getPoints().get(i + 1);
float z = this.getPoints().get(i + 2); float z = this.getPoints().get(i + 2);
System.out.println(String.format("Point at %d is x:%f, y:%f, z:%f", index, x, y, z));
} }
/** /**
@ -121,4 +130,4 @@ public class Plane3D extends TriangleMesh{
} }
} }

@ -1,65 +1,67 @@
package visualiser.layout; package visualiser.layout;
import javafx.geometry.Point3D;
import javafx.scene.image.Image; import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.image.PixelWriter; import javafx.scene.image.PixelWriter;
import javafx.scene.image.WritableImage; import javafx.scene.image.WritableImage;
import javafx.scene.paint.Color; import javafx.scene.paint.Color;
import javafx.scene.paint.PhongMaterial; import javafx.scene.paint.PhongMaterial;
import javafx.scene.shape.Box;
import javafx.scene.shape.MeshView; import javafx.scene.shape.MeshView;
import javafx.scene.shape.Shape3D;
import javafx.scene.shape.TriangleMesh;
import visualiser.utils.PerlinNoiseGenerator; import visualiser.utils.PerlinNoiseGenerator;
/** /**
* Creates a SeaSurface * Creates a SeaSurface
*/ */
public class SeaSurface { public class SeaSurface extends Subject3D {
private float[][] noiseArray; private static Image image;
private Subject3D surface;
/** /**
* Sea Surface Constructor * Sea Surface Constructor
*
* @param size size of the sea surface (has to be square for simplicity's sake) * @param size size of the sea surface (has to be square for simplicity's sake)
* @param freq frequency the perlin noise is to be generated at * @param freq frequency the perlin noise is to be generated at
* @param x offset that the sea should be set at position-wise
* @param y offset that the sea should be set at position-wise
* @param z offset that the sea should be set at position-wise
*/ */
public SeaSurface(int size, double freq, double x, double y, double z){ public SeaSurface(int size, double freq) {
noiseArray = PerlinNoiseGenerator.createNoise(size, freq); super(createSurface(size, freq), 0);
createSurface(); image = new Image(getClass().getClassLoader().getResourceAsStream("images/skybox/ThickCloudsWaterDown2048.png"));
surface.setZ(z);
surface.setY(y);
surface.setX(x);
} }
/** /**
* Creates the sea surface * Creates the sea surface
* @param size size of sea noise array.
* @param freq frequency of sea noise array.
* @return The sea surface.
*/ */
private void createSurface(){ private static Shape3D createSurface(int size, double freq) {
Image diffuseMap = createImage(noiseArray.length, noiseArray); float[][] noiseArray = PerlinNoiseGenerator.createNoise(size, freq);
Image diffuseMap = createImage(noiseArray.length, noiseArray); //image
PhongMaterial material = new PhongMaterial(); PhongMaterial material = new PhongMaterial();
material.setDiffuseColor(Color.web("#FFFFFF"));
material.setSpecularColor(Color.web("#000000"));
material.setDiffuseMap(diffuseMap); material.setDiffuseMap(diffuseMap);
//material.setSpecularColor(Color.WHITE);
Plane3D seaPlane = new Plane3D(noiseArray.length, noiseArray.length, 10, 10); Plane3D seaPlane = new Plane3D(noiseArray.length, noiseArray.length, 10, 10);
MeshView seaSurface = new MeshView(seaPlane); MeshView seaSurface = new MeshView(seaPlane);
// Box seaSurface = new Box(noiseArray.length, 0.1, noiseArray.length);
seaSurface.setMaterial(material); seaSurface.setMaterial(material);
seaSurface.setMouseTransparent(true); seaSurface.setMouseTransparent(true);
seaSurface.toFront(); seaSurface.toFront();
//seaSurface.setRotationAxis(new Point3D(1, 0, 0));
//seaSurface.setRotate(90);
surface = new Subject3D(seaSurface); return seaSurface;
} }
/** /**
* Create texture for uv mapping * Create texture for uv mapping
* @param size size of the image to make *
* @param size size of the image to make
* @param noise array of noise * @param noise array of noise
* @return image that is created * @return image that is created
*/ */
private Image createImage(double size, float[][] noise) { private static Image createImage(double size, float[][] noise) {
int width = (int) size; int width = (int) size;
int height = (int) size; int height = (int) size;
@ -94,9 +96,10 @@ public class SeaSurface {
/** /**
* Nomalises the values so that the colours are correct * Nomalises the values so that the colours are correct
* @param value value to normalise *
* @param min current min * @param value value to normalise
* @param max current max * @param min current min
* @param max current max
* @param newMin new min * @param newMin new min
* @param newMax new max * @param newMax new max
* @return returns normalised value * @return returns normalised value
@ -109,9 +112,10 @@ public class SeaSurface {
/** /**
* clamps a value between a min and max * clamps a value between a min and max
*
* @param value value to clamp * @param value value to clamp
* @param min minimum value it can be * @param min minimum value it can be
* @param max maximum value it can be * @param max maximum value it can be
* @return result after clamp * @return result after clamp
*/ */
private static double clamp(double value, double min, double max) { private static double clamp(double value, double min, double max) {
@ -126,11 +130,11 @@ public class SeaSurface {
} }
/** /**
* Get surface * Prevent rescaling of sea surface
* @return the surface so it can be drawn *
* @param scale ignored
*/ */
public Subject3D getSurface(){ @Override
return surface; public void setScale(double scale) {
} }
}
}

@ -0,0 +1,17 @@
package visualiser.layout;
import javafx.scene.paint.Color;
import javafx.scene.paint.PhongMaterial;
import javafx.scene.shape.Cylinder;
import javafx.scene.transform.Rotate;
/**
* Created by cbt24 on 14/09/17.
*/
public class Shockwave extends Subject3D {
public Shockwave(double radius) {
super(new Cylinder(radius,0),0);
getMesh().getTransforms().add(new Rotate(-90, Rotate.X_AXIS));
getMesh().setMaterial(new PhongMaterial(new Color(0,0,0,0)));
}
}

@ -0,0 +1,163 @@
package visualiser.layout;
import javafx.geometry.Point3D;
import javafx.scene.image.Image;
import javafx.scene.paint.Color;
import javafx.scene.paint.PhongMaterial;
import javafx.scene.shape.MeshView;
import javafx.scene.transform.Rotate;
import java.util.ArrayList;
import java.util.List;
/**
* Creates a skyBox
*/
public class SkyBox {
private int size;
private double x;
private double y;
private double z;
private double freq;
private double yshift;
private double clipOverlap;
private List<Subject3D> skyBoxPlanes = new ArrayList<>();
public SkyBox(int edgeLength, double freq, double x, double y, double z) {
this.size = edgeLength;
this.x = x;
this.y = y;
this.z = z;
this.freq = freq;
this.yshift = -size/64;
clipOverlap = 0;
makeSkyBox();
}
private void makeSkyBox() {
addTop();
addFront();
addBack();
addLeft();
addRight();
//addSeaOverlay();
}
private void addTop() {
MeshView surface = makeSurface(new Image(getClass().getClassLoader().getResourceAsStream("images/skybox/ThickCloudsWaterUp2048.png")), size);
surface.setRotationAxis(new Point3D(0, 0, 1));
surface.setRotate(180);
surface.setTranslateX(x);
surface.setTranslateY(y - size + 1);
surface.setTranslateZ(z);
Subject3D top = new SkyBoxPlane(surface,0);
skyBoxPlanes.add(top);
}
private void addRight() {
MeshView surface = makeSurface(new Image(getClass().getClassLoader().getResourceAsStream("images/skybox/ThickCloudsWaterRight2048.png")), size + 1);
surface.setTranslateX(size/2);
surface.setTranslateY(size/2);
surface.setRotationAxis(new Point3D(1, 0, 0));
surface.setRotate(90);
surface.setTranslateX(-size/2);
surface.setTranslateY(-size/2);
surface.setTranslateX(x);
surface.setTranslateY(y + yshift);
surface.setTranslateZ(z + size/2 - clipOverlap);
Subject3D right = new SkyBoxPlane(surface,0);
skyBoxPlanes.add(right);
}
private void addLeft() {
MeshView surface = makeSurface(new Image(getClass().getClassLoader().getResourceAsStream("images/skybox/ThickCloudsWaterLeft2048.png")), size + 1);
surface.setTranslateX(size/2);
surface.setTranslateY(size/2);
surface.setRotationAxis(new Point3D(1, 0, 0));
surface.setRotate(-90);
surface.setTranslateX(-size/2);
surface.setTranslateY(-size/2);
surface.setScaleX(-1);
surface.setScaleZ(-1);
surface.setTranslateX(x);
surface.setTranslateY(y + yshift);
surface.setTranslateZ(z - size/2 + clipOverlap);
Subject3D left = new SkyBoxPlane(surface,0);
skyBoxPlanes.add(left);
}
private void addBack() {
MeshView surface = makeSurface(new Image(getClass().getClassLoader().getResourceAsStream("images/skybox/ThickCloudsWaterBack2048.png")), size);
surface.getTransforms().add(new Rotate(90, 0, 0));
surface.setRotationAxis(new Point3D(1, 0, 0));
surface.setRotate(-90);
surface.setScaleY(-1);
surface.setScaleZ(-1);
surface.setTranslateX(x - size/2 + clipOverlap);
surface.setTranslateY(y + yshift);
surface.setTranslateZ(z);
Subject3D back = new SkyBoxPlane(surface,0);
skyBoxPlanes.add(back);
}
private void addFront() {
MeshView surface = makeSurface(new Image(getClass().getClassLoader().getResourceAsStream("images/skybox/ThickCloudsWaterFront2048.png")), size);
surface.setTranslateX(size/2);
surface.setTranslateY(size/2);
surface.setRotationAxis(new Point3D(0, 0, 1));
surface.setRotate(-90);
surface.setTranslateX(-size/2);
surface.setTranslateY(-size/2);
surface.setTranslateX(x + size/2 - clipOverlap);
surface.setTranslateY(y + yshift);
surface.setTranslateZ(z);
Subject3D front = new SkyBoxPlane(surface,0);
skyBoxPlanes.add(front);
}
private MeshView makeSurface(Image diffuseMap, int size) {
PhongMaterial material = new PhongMaterial();
material.setDiffuseColor(Color.web("#FFFFFF"));
material.setSpecularColor(Color.web("#000000"));
material.setDiffuseMap(diffuseMap);
Plane3D plane = new Plane3D(size, size, 10, 10);
MeshView surface = new MeshView(plane);
surface.setMaterial(material);
surface.setMouseTransparent(true);
return surface;
}
private void addSeaOverlay() {
SeaSurface seaSurface = new SeaSurface(size * 3, freq);
seaSurface.setX(x);
seaSurface.setY(y - size/4 + 1);
seaSurface.setZ(z);
skyBoxPlanes.add(seaSurface);
}
public List<Subject3D> getSkyBoxPlanes() {
return skyBoxPlanes;
}
}

@ -0,0 +1,14 @@
package visualiser.layout;
import javafx.scene.shape.Shape3D;
public class SkyBoxPlane extends Subject3D {
public SkyBoxPlane(Shape3D mesh, int sourceID) {
super(mesh,sourceID);
}
@Override
public void setScale(double scale) {
}
}

@ -2,6 +2,7 @@ package visualiser.layout;
import javafx.scene.shape.Shape3D; import javafx.scene.shape.Shape3D;
import javafx.scene.transform.Rotate; import javafx.scene.transform.Rotate;
import javafx.scene.transform.Scale;
import javafx.scene.transform.Translate; import javafx.scene.transform.Translate;
/** /**
@ -12,6 +13,10 @@ public class Subject3D {
* Rendered mesh * Rendered mesh
*/ */
private Shape3D mesh; private Shape3D mesh;
/**
* Source ID of subject in game model
*/
private int sourceID;
/** /**
* Position translation updated by state listeners * Position translation updated by state listeners
@ -23,22 +28,30 @@ public class Subject3D {
*/ */
private Rotate heading; private Rotate heading;
private Scale scale;
/** /**
* Constructor for view subject wrapper * Constructor for view subject wrapper
* @param mesh to be rendered * @param mesh to be rendered
* @param sourceID Source ID of the subject.
*/ */
public Subject3D(Shape3D mesh) { public Subject3D(Shape3D mesh, int sourceID) {
this.mesh = mesh; this.mesh = mesh;
this.sourceID = sourceID;
this.scale = new Scale();
this.position = new Translate(); this.position = new Translate();
this.heading = new Rotate(0, Rotate.Y_AXIS); this.heading = new Rotate(0, Rotate.Y_AXIS);
this.mesh.getTransforms().addAll(position, scale, heading, new Rotate(90, Rotate.X_AXIS), new Rotate(180, Rotate.Y_AXIS));
this.mesh.getTransforms().addAll(position, heading, new Rotate(90, Rotate.X_AXIS), new Rotate(180, Rotate.Y_AXIS));
} }
public Shape3D getMesh() { public Shape3D getMesh() {
return mesh; return mesh;
} }
public int getSourceID() {
return sourceID;
}
public Translate getPosition() { public Translate getPosition() {
return this.position; return this.position;
} }
@ -47,6 +60,12 @@ public class Subject3D {
return heading; return heading;
} }
public void setScale(double scale) {
this.scale.setX(scale);
this.scale.setY(scale);
this.scale.setZ(scale);
}
public void setX(double x) { public void setX(double x) {
position.setX(x); position.setX(x);
} }

@ -1,11 +1,12 @@
package visualiser.layout; package visualiser.layout;
import javafx.animation.AnimationTimer;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.value.ChangeListener; import javafx.beans.value.ChangeListener;
import javafx.collections.ListChangeListener; import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList; import javafx.collections.ObservableList;
import javafx.scene.Group; import javafx.scene.*;
import javafx.scene.PerspectiveCamera;
import javafx.scene.SubScene;
import javafx.scene.input.PickResult; import javafx.scene.input.PickResult;
import javafx.scene.layout.Pane; import javafx.scene.layout.Pane;
import javafx.scene.paint.Color; import javafx.scene.paint.Color;
@ -33,11 +34,15 @@ public class View3D extends Pane {
/** /**
* Map for selecting Subject3D from Shape3D * Map for selecting Subject3D from Shape3D
*/ */
private Map<Shape3D, Subject3D> selectionMap; private Map<Shape3D, Subject3D> shapeMap;
/**
* Map for selecting Subject3D from source ID
*/
private Map<Integer, Subject3D> sourceMap;
/** /**
* Subject tracked by camera * Subject tracked by camera
*/ */
private Subject3D target; private ObjectProperty<Subject3D> target;
/** /**
* Rendering container for shapes * Rendering container for shapes
*/ */
@ -67,44 +72,43 @@ public class View3D extends Pane {
*/ */
private Rotate pitch; private Rotate pitch;
/** /**
* Single listener for subject heading changes * Animation loop for camera tracking
*/
private ChangeListener<? super Number> pivotHeading = (o, prev, curr) -> yaw.setAngle((double)curr);
/**
* Single listener for subject position (x) changes
*/
private ChangeListener<? super Number> pivotX = (o, prev, curr) -> pivot.setX((double)curr);
/**
* Single listener for subject position (y) changes
*/ */
private ChangeListener<? super Number> pivotY = (o, prev, curr) -> pivot.setY((double)curr); private AnimationTimer trackBoat;
/** /**
* Single listener for subject position (z) changes * Distance to switch from third person to bird's eye
*/ */
private ChangeListener<? super Number> pivotZ = (o, prev, curr) -> pivot.setZ((double)curr); private final double THIRD_PERSON_LIMIT = 100;
/** /**
* Distance to switch from third person to bird's eye * Distance to stop zoom
*/ */
private double THIRD_PERSON_LIMIT = 100; private final double FIRST_PERSON_LIMIT = 2;
/** /**
* Default constructor for View3D. Sets up Scene and PerspectiveCamera. * Default constructor for View3D. Sets up Scene and PerspectiveCamera.
* @param fill whether or not to fill the background of the view.
*/ */
public View3D() { public View3D(boolean fill) {
this.world = new Group(); this.world = new Group();
this.selectionMap = new HashMap<>(); this.shapeMap = new HashMap<>();
this.target = null; this.sourceMap = new HashMap<>();
this.target = new SimpleObjectProperty<>(null);
this.scene = new SubScene(world, 300, 300); this.scene = new SubScene(world, 300, 300);
scene.widthProperty().bind(this.widthProperty()); scene.widthProperty().bind(this.widthProperty());
scene.heightProperty().bind(this.heightProperty()); scene.heightProperty().bind(this.heightProperty());
scene.setFill(new Color(0.2, 0.6, 1, 1)); if (fill) {
scene.setFill(new Color(0.2, 0.6, 1, 1));
}
scene.setCamera(buildCamera()); scene.setCamera(buildCamera());
this.getChildren().add(scene); this.getChildren().add(scene);
} }
public View3D(){
this(true);
}
/** /**
* Sets up camera view frustum and binds transformations * Sets up camera view frustum and binds transformations
* @return perspective camera * @return perspective camera
@ -140,17 +144,23 @@ public class View3D extends Pane {
if (c.wasRemoved() || c.wasAdded()) { if (c.wasRemoved() || c.wasAdded()) {
for (Subject3D shape : c.getRemoved()) { for (Subject3D shape : c.getRemoved()) {
world.getChildren().remove(shape.getMesh()); world.getChildren().remove(shape.getMesh());
selectionMap.remove(shape.getMesh()); shapeMap.remove(shape.getMesh());
sourceMap.remove(shape.getSourceID());
} }
for (Subject3D shape : c.getAddedSubList()) { for (Subject3D shape : c.getAddedSubList()) {
world.getChildren().add(shape.getMesh()); world.getChildren().add(shape.getMesh());
selectionMap.put(shape.getMesh(), shape); shapeMap.put(shape.getMesh(), shape);
sourceMap.put(shape.getSourceID(), shape);
} }
} }
} }
}); });
} }
public Subject3D getShape(int sourceID) {
return sourceMap.get(sourceID);
}
/** /**
* Intercept mouse clicks on subjects in view. The applied listener cannot be removed. * Intercept mouse clicks on subjects in view. The applied listener cannot be removed.
*/ */
@ -158,20 +168,48 @@ public class View3D extends Pane {
scene.setOnMousePressed(e -> { scene.setOnMousePressed(e -> {
PickResult result = e.getPickResult(); PickResult result = e.getPickResult();
if(result != null && result.getIntersectedNode() != null && result.getIntersectedNode() instanceof Shape3D) { if(result != null && result.getIntersectedNode() != null && result.getIntersectedNode() instanceof Shape3D) {
trackSubject(selectionMap.get(result.getIntersectedNode())); untrackSubject();
trackSubject(shapeMap.get(result.getIntersectedNode()));
setThirdPerson();
} }
}); });
} }
public ObjectProperty<Subject3D> targetProperty() {
return target;
}
/**
* Configures camera to third person view
*/
public void setThirdPerson() {
this.setDistance(THIRD_PERSON_LIMIT / 2);
this.setPitch(10);
for(Subject3D item: items) {
item.setScale(0.1);
}
}
/**
* Configures camera to bird's eye view
*/
public void setBirdsEye() {
this.setYaw(0);
this.setPitch(60);
for(Subject3D item: items) {
item.setScale(1);
}
}
/** /**
* Stop camera from following the last selected subject * Stop camera from following the last selected subject
*/ */
private void untrackSubject() { private void untrackSubject() {
if(target != null) { if(target.get() != null) {
target.getPosition().xProperty().removeListener(pivotX); trackBoat.stop();
target.getPosition().yProperty().removeListener(pivotY); target.setValue(null);
target.getPosition().zProperty().removeListener(pivotZ);
target.getHeading().angleProperty().removeListener(pivotHeading);
} }
} }
@ -180,19 +218,16 @@ public class View3D extends Pane {
* @param subject to track * @param subject to track
*/ */
private void trackSubject(Subject3D subject) { private void trackSubject(Subject3D subject) {
untrackSubject(); target.set(subject);
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.trackBoat = new AnimationTimer() {
this.setPitch(20); @Override
public void handle(long now) {
updatePivot(target.get().getPosition());
setYaw(target.get().getHeading().getAngle());
}
};
trackBoat.start();
} }
public void setNearClip(double nearClip) { public void setNearClip(double nearClip) {
@ -203,6 +238,10 @@ public class View3D extends Pane {
this.farClip = farClip; this.farClip = farClip;
} }
public Translate getPivot() {
return pivot;
}
/** /**
* Sets the coordinates of the camera pivot once. * Sets the coordinates of the camera pivot once.
* @param pivot source of coordinates * @param pivot source of coordinates
@ -230,13 +269,12 @@ public class View3D extends Pane {
public void updateDistance(double delta) { public void updateDistance(double delta) {
double distance = -this.distance.getZ() + delta; double distance = -this.distance.getZ() + delta;
if(distance <= 0) { if(distance <= FIRST_PERSON_LIMIT) {
this.setDistance(0); this.setDistance(FIRST_PERSON_LIMIT);
} else if(distance > THIRD_PERSON_LIMIT) { } else if(distance > THIRD_PERSON_LIMIT) {
untrackSubject();
this.setYaw(0);
this.setPitch(60);
this.setDistance(distance); this.setDistance(distance);
untrackSubject();
setBirdsEye();
} else { } else {
this.setDistance(distance); this.setDistance(distance);
} }
@ -257,4 +295,12 @@ public class View3D extends Pane {
public void setPitch(double pitch) { public void setPitch(double pitch) {
this.pitch.setAngle(-pitch); this.pitch.setAngle(-pitch);
} }
public void addAmbientLight(AmbientLight ambientLight) {
this.world.getChildren().add(ambientLight);
}
public void addPointLight(PointLight pointLight) {
this.world.getChildren().add(pointLight);
}
} }

@ -1,6 +1,8 @@
package visualiser.model; package visualiser.model;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.ObjectProperty; import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleObjectProperty; import javafx.beans.property.SimpleObjectProperty;
import javafx.scene.paint.Color; import javafx.scene.paint.Color;
import network.Messages.Enums.BoatStatusEnum; import network.Messages.Enums.BoatStatusEnum;
@ -62,6 +64,7 @@ public class VisualiserBoat extends Boat {
private ObjectProperty<GPSCoordinate> positionProperty; private ObjectProperty<GPSCoordinate> positionProperty;
private ObjectProperty<Bearing> bearingProperty; private ObjectProperty<Bearing> bearingProperty;
private BooleanProperty hasCollided;
/** /**
@ -74,6 +77,7 @@ public class VisualiserBoat extends Boat {
super(boat.getSourceID(), boat.getName(), boat.getCountry()); super(boat.getSourceID(), boat.getName(), boat.getCountry());
this.color = color; this.color = color;
this.hasCollided = new SimpleBooleanProperty(false);
} }
@ -253,10 +257,6 @@ public class VisualiserBoat extends Boat {
this.positionProperty.set(position); this.positionProperty.set(position);
} }
public ObjectProperty<GPSCoordinate> positionProperty() {
return positionProperty;
}
@Override @Override
public Bearing getBearing() { public Bearing getBearing() {
return bearingProperty.get(); return bearingProperty.get();
@ -270,7 +270,15 @@ public class VisualiserBoat extends Boat {
this.bearingProperty.set(bearing); this.bearingProperty.set(bearing);
} }
public ObjectProperty<Bearing> bearingProperty() { public boolean hasCollided() {
return bearingProperty; return hasCollided.get();
}
public BooleanProperty hasCollidedProperty() {
return hasCollided;
}
public void setHasCollided(boolean hasCollided) {
this.hasCollided.set(hasCollided);
} }
} }

@ -65,7 +65,7 @@ public class VisualiserRaceController implements RunnableWithFramePeriod {
compositeRaceCommand.addCommand(command); compositeRaceCommand.addCommand(command);
} catch (CommandConstructionException e) { } catch (CommandConstructionException e) {
Logger.getGlobal().log(Level.WARNING, "VisualiserRaceController could not create a command for incoming message."); //Logger.getGlobal().log(Level.WARNING, "VisualiserRaceController could not create a command for incoming message.");
} catch (InterruptedException e) { } catch (InterruptedException e) {
Logger.getGlobal().log(Level.SEVERE, "VisualiserRaceController was interrupted on thread: " + Thread.currentThread() + " while waiting for messages."); Logger.getGlobal().log(Level.SEVERE, "VisualiserRaceController was interrupted on thread: " + Thread.currentThread() + " while waiting for messages.");

@ -101,4 +101,14 @@ public class GPSConverter {
return convertGPS(coordinate.getLatitude(), coordinate.getLongitude()); return convertGPS(coordinate.getLatitude(), coordinate.getLongitude());
} }
/**
* Gets the bearing between two coordinates
* @param coord1 coordinate 1
* @param coord2 coordinate 2
* @return return the bearing between the two.
*/
public static double getAngle(GraphCoordinate coord1, GraphCoordinate coord2){
return Math.atan2(coord2.getX() - coord1.getX(), coord2.getY() - coord1.getY());
}
} }

@ -85,4 +85,4 @@ public class PerlinNoiseGenerator {
static { for (int i=0; i < 256 ; i++) p[256+i] = p[i] = permutation[i]; } static { for (int i=0; i < 256 ; i++) p[256+i] = p[i] = permutation[i]; }
} }
} }

@ -89,3 +89,15 @@
-fx-focus-color: transparent; -fx-focus-color: transparent;
-fx-background-color: transparent; -fx-background-color: transparent;
} }
#lineChartWrapper{
-fx-border-color: #02378c;
-fx-background-color: #4783e0;
-fx-border-width: 3;
}
#boatInfoTable{
-fx-border-color: #012256;
-fx-border-width: 3;
}

@ -91,3 +91,14 @@
-fx-focus-color: transparent; -fx-focus-color: transparent;
-fx-background-color: transparent; -fx-background-color: transparent;
} }
#lineChartWrapper{
-fx-border-color: #02378c;
-fx-background-color: #012256;
-fx-border-width: 3;
}
#boatInfoTable{
-fx-border-color: #012256;
-fx-border-width: 3;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 103 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 85 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 99 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

@ -0,0 +1,51 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<Race>
<RaceID>5326</RaceID>
<RaceType>FLEET</RaceType>
<CreationTimeDate>RACE_CREATION_TIME</CreationTimeDate>
<RaceStartTime Postpone="false" Time="RACE_START_TIME"/>
<Participants>
</Participants>
<CompoundMarkSequence>
<Corner SeqID="1" CompoundMarkID="1" Rounding="SP" ZoneSize="3" />
<Corner SeqID="2" CompoundMarkID="2" Rounding="Port" ZoneSize="3" />
<Corner SeqID="3" CompoundMarkID="4" Rounding="Port" ZoneSize="3" />
<Corner SeqID="4" CompoundMarkID="3" Rounding="Starboard" ZoneSize="3" />
<Corner SeqID="5" CompoundMarkID="4" Rounding="Port" ZoneSize="3" />
<Corner SeqID="6" CompoundMarkID="5" Rounding="SP" ZoneSize="3"/>
</CompoundMarkSequence>
<Course>
<CompoundMark CompoundMarkID="1" Name="Start Line">
<Mark SeqId="1" Name="PRO" TargetLat="32.296577" TargetLng="-64.854304" SourceID="101"/>
<Mark SeqId="2" Name="PIN" TargetLat="32.293771" TargetLng="-64.855242" SourceID="102"/>
</CompoundMark>
<CompoundMark CompoundMarkID="2" Name="Marker 1">
<Mark Name="Marker1" TargetLat="32.293039" TargetLng="-64.843983" SourceID="103"/>
</CompoundMark>
<CompoundMark CompoundMarkID="3" Name="Windward Gate">
<Mark Name="WGL" SeqId="1" TargetLat="32.28468" TargetLng="-64.850045" SourceID="104"/>
<Mark Name="WGR" SeqId="2" TargetLat="32.280164" TargetLng="-64.847591" SourceID="105"/>
</CompoundMark>
<CompoundMark CompoundMarkID="4" Name="Leeward Gate">
<Mark Name="LGL" SeqId="1" TargetLat="32.309693" TargetLng="-64.835249" SourceID="106"/>
<Mark Name="LGR" SeqId="2" TargetLat="32.308046" TargetLng="-64.831785" SourceID="107"/>
</CompoundMark>
<CompoundMark CompoundMarkID="5" Name="Finish Line">
<Mark Name="FL" SeqId="1" TargetLat="32.317379" TargetLng="-64.839291" SourceID="108"/>
<Mark Name="FR" SeqId="2" TargetLat="32.317257" TargetLng="-64.83626" SourceID="109"/>
</CompoundMark>
</Course>
<CourseLimit>
<Limit Lat="32.313922" Lon="-64.837168" SeqID="1"/>
<Limit Lat="32.317379" Lon="-64.839291" SeqID="2"/>
<Limit Lat="32.317911" Lon="-64.836996" SeqID="3"/>
<Limit Lat="32.317257" Lon="-64.83626" SeqID="4"/>
<Limit Lat="32.304273" Lon="-64.822834" SeqID="5"/>
<Limit Lat="32.279097" Lon="-64.841545" SeqID="6"/>
<Limit Lat="32.279604" Lon="-64.849871" SeqID="7"/>
<Limit Lat="32.289545" Lon="-64.854162" SeqID="8"/>
<Limit Lat="32.290198" Lon="-64.858711" SeqID="9"/>
<Limit Lat="32.297164" Lon="-64.856394" SeqID="10"/>
<Limit Lat="32.296148" Lon="-64.849184" SeqID="11"/>
</CourseLimit>
</Race>

@ -0,0 +1,148 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.chart.LineChart?>
<?import javafx.scene.chart.NumberAxis?>
<?import javafx.scene.control.Accordion?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.CheckBox?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.RadioButton?>
<?import javafx.scene.control.Separator?>
<?import javafx.scene.control.SplitPane?>
<?import javafx.scene.control.TableColumn?>
<?import javafx.scene.control.TableView?>
<?import javafx.scene.control.TitledPane?>
<?import javafx.scene.control.ToggleGroup?>
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.layout.ColumnConstraints?>
<?import javafx.scene.layout.GridPane?>
<?import javafx.scene.layout.Pane?>
<?import javafx.scene.layout.RowConstraints?>
<?import javafx.scene.layout.StackPane?>
<?import javafx.scene.text.Font?>
<SplitPane fx:id="racePane" prefHeight="431.0" prefWidth="610.0" visible="true" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0" xmlns="http://javafx.com/javafx/8.0.65" xmlns:fx="http://javafx.com/fxml/1" fx:controller="visualiser.Controllers.RaceViewController">
<items>
<StackPane fx:id="newPane" prefHeight="150.0" prefWidth="200.0">
<children>
<AnchorPane focusTraversable="true">
<children>
<GridPane fx:id="canvasBase" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
<columnConstraints>
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" />
</columnConstraints>
<rowConstraints>
<RowConstraints minHeight="10.0" vgrow="SOMETIMES" />
</rowConstraints>
</GridPane>
<Pane prefHeight="200.0" prefWidth="400.0" visible="false">
<children>
<Accordion>
<panes>
<TitledPane animated="false" prefHeight="395.0" prefWidth="222.0" text="Annotation Control">
<content>
<AnchorPane fx:id="annotationPane" minHeight="0.0" minWidth="0.0">
<children>
<CheckBox fx:id="showName" layoutY="39.0" mnemonicParsing="false" selected="true" text="Show Boat Name" AnchorPane.leftAnchor="0.0" AnchorPane.topAnchor="0.0" />
<CheckBox fx:id="showAbbrev" layoutY="61.0" mnemonicParsing="false" selected="true" text="Show Boat Abbreviation" AnchorPane.leftAnchor="0.0" AnchorPane.topAnchor="25.0" />
<CheckBox fx:id="showSpeed" layoutY="90.0" mnemonicParsing="false" selected="true" text="Show Boat Speed" AnchorPane.leftAnchor="0.0" AnchorPane.topAnchor="50.0" />
<CheckBox fx:id="showBoatPath" mnemonicParsing="false" selected="true" text="Show Boat Paths" AnchorPane.leftAnchor="0.0" AnchorPane.topAnchor="75.0" />
<CheckBox fx:id="showTime" mnemonicParsing="false" selected="true" text="Show Boat Leg Time" AnchorPane.leftAnchor="0.0" AnchorPane.topAnchor="100.0" />
<CheckBox fx:id="showEstTime" mnemonicParsing="false" selected="true" text="Show Est. Time to Next Mark" AnchorPane.leftAnchor="0.0" AnchorPane.topAnchor="125.0" />
<CheckBox fx:id="showGuideline" mnemonicParsing="false" text="Show Guideline" AnchorPane.leftAnchor="0.0" AnchorPane.topAnchor="150.0" />
<Separator layoutX="19.6" layoutY="175.6" prefHeight="0.0" prefWidth="200.0" AnchorPane.leftAnchor="10.0" AnchorPane.topAnchor="175.0" />
<Label text="Annotations" AnchorPane.leftAnchor="0.0" AnchorPane.topAnchor="175.0" />
<RadioButton fx:id="hideAnnoRBtn" mnemonicParsing="false" text="Hidden" AnchorPane.leftAnchor="0.0" AnchorPane.topAnchor="200.0">
<toggleGroup>
<ToggleGroup fx:id="annoToggleGroup" />
</toggleGroup>
</RadioButton>
<RadioButton fx:id="showAnnoRBtn" mnemonicParsing="false" text="Visible" toggleGroup="$annoToggleGroup" AnchorPane.leftAnchor="0.0" AnchorPane.topAnchor="225.0" />
<RadioButton fx:id="partialAnnoRBtn" mnemonicParsing="false" text="Partial" toggleGroup="$annoToggleGroup" AnchorPane.leftAnchor="0.0" AnchorPane.topAnchor="250.0" />
<RadioButton fx:id="importantAnnoRBtn" mnemonicParsing="false" text="Important" toggleGroup="$annoToggleGroup" AnchorPane.leftAnchor="0.0" AnchorPane.topAnchor="275.0" />
<Button fx:id="saveAnno" layoutX="11.0" layoutY="126.0" mnemonicParsing="false" text="Save Important Annotations" AnchorPane.leftAnchor="0.0" AnchorPane.topAnchor="300.0" />
</children>
</AnchorPane>
</content>
</TitledPane>
<TitledPane animated="false" text="FPS Control">
<content>
<AnchorPane minHeight="0.0" minWidth="0.0" prefHeight="180.0" prefWidth="200.0">
<children>
<CheckBox fx:id="showFPS" layoutX="-14.0" layoutY="13.0" mnemonicParsing="false" selected="true" text="Show FPS" AnchorPane.leftAnchor="0.0" AnchorPane.topAnchor="0.0" />
</children>
</AnchorPane>
</content>
</TitledPane>
</panes>
</Accordion>
</children>
</Pane>
<Label fx:id="timer" maxHeight="20.0" text="0:0" AnchorPane.bottomAnchor="0.0" AnchorPane.rightAnchor="0.0">
<font>
<Font name="System Bold" size="15.0" />
</font>
</Label>
<Label fx:id="FPS" text="FPS: 0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0">
<font>
<Font name="System Bold" size="15.0" />
</font>
</Label>
<Label fx:id="timeZone" text="Label" AnchorPane.bottomAnchor="0.0" AnchorPane.rightAnchor="0.0">
<font>
<Font name="System Bold" size="15.0" />
</font>
<padding>
<Insets bottom="20.0" />
</padding>
</Label>
<StackPane fx:id="arrowPane" alignment="TOP_RIGHT" mouseTransparent="true" prefHeight="150.0" prefWidth="150.0" snapToPixel="false" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
<children>
<fx:include fx:id="arrow" source="arrow.fxml" />
</children>
</StackPane>
<Label fx:id="tutorialText" alignment="CENTER" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" style="-fx-border-color: orange; -fx-border-radius: 5px; -fx-background-color: #ffffcc; -fx-text-fill: #3399ff; -fx-border-width: 3; -fx-border-insets: -3;" text="This is the tutorial text" visible="false" AnchorPane.leftAnchor="150.0" AnchorPane.rightAnchor="150.0" AnchorPane.topAnchor="100.0" />
</children>
</AnchorPane>
<AnchorPane fx:id="infoWrapper" focusTraversable="true" minHeight="0.0" minWidth="0.0" prefHeight="160.0" prefWidth="200.0" visible="false">
<children>
<GridPane layoutX="-2.0" AnchorPane.bottomAnchor="40.0" AnchorPane.leftAnchor="40.0" AnchorPane.rightAnchor="40.0" AnchorPane.topAnchor="40.0">
<columnConstraints>
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="100.0" />
<ColumnConstraints hgrow="SOMETIMES" maxWidth="475.0" minWidth="475.0" prefWidth="475.0" />
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="100.0" />
</columnConstraints>
<rowConstraints>
<RowConstraints maxHeight="330.0" minHeight="10.0" prefHeight="214.0" vgrow="SOMETIMES" />
<RowConstraints maxHeight="150.0" minHeight="10.0" prefHeight="135.0" vgrow="SOMETIMES" />
</rowConstraints>
<children>
<TableView fx:id="boatInfoTable" minWidth="475.0" prefHeight="214.0" prefWidth="475.0" GridPane.columnIndex="1" GridPane.halignment="CENTER" GridPane.valignment="CENTER">
<columns>
<TableColumn fx:id="boatPlacingColumn" prefWidth="50.0" text="Place" />
<TableColumn fx:id="boatTeamColumn" prefWidth="200.0" text="Team" />
<TableColumn fx:id="boatMarkColumn" prefWidth="150.0" text="Mark" />
<TableColumn fx:id="boatSpeedColumn" prefWidth="75.0" text="Speed" />
</columns>
</TableView>
<AnchorPane fx:id="lineChartWrapper" prefHeight="167.0" prefWidth="178.0" GridPane.columnIndex="1" GridPane.halignment="CENTER" GridPane.rowIndex="1" GridPane.valignment="CENTER">
<children>
<LineChart fx:id="sparklineChart" layoutX="-211.0" layoutY="-186.0" mouseTransparent="true" prefHeight="167.0" prefWidth="178.0" titleSide="LEFT" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
<xAxis>
<NumberAxis side="BOTTOM" fx:id="xAxis" />
</xAxis>
<yAxis>
<NumberAxis fx:id="yAxis" side="LEFT" />
</yAxis>
</LineChart>
</children>
</AnchorPane>
</children>
</GridPane>
</children>
</AnchorPane>
</children>
</StackPane>
</items>
</SplitPane>
Loading…
Cancel
Save