Merge branch 'master' into new_3d_assets

# Conflicts:
#	racevisionGame/src/main/java/visualiser/Controllers/RaceViewController.java
main
Fan-Wu Yang 8 years ago
commit 8a6997f29b

@ -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, 0);
Event raceEvent = new Event(false, 0, 5);
} catch (Exception e) {

@ -40,24 +40,17 @@ public class Event {
private String raceXML;
private String regattaXML;
private String boatXML;
private XMLFileType xmlFileType;
private Polars boatPolars;
/**
* Data sources containing data from the xml files.
*/
RaceDataSource raceDataSource;
BoatDataSource boatDataSource;
RegattaDataSource regattaDataSource;
private RaceDataSource raceDataSource;
private BoatDataSource boatDataSource;
private RegattaDataSource regattaDataSource;
private ConnectionAcceptor connectionAcceptor;
private LatestMessages latestMessages;
private CompositeCommand compositeCommand;
/**
* This is used to allocate source IDs.
*/
@ -66,22 +59,18 @@ public class Event {
private RaceLogic raceLogic;
private Thread raceThread;
private Thread connectionThread;
private int mapIndex;
/**
* Constructs an event, using various XML files.
* @param singlePlayer Whether or not to create a single player event.
* @param mapIndex Specifies which map to use.
* @param raceLength The length of the race, in milliseconds.
* @throws EventConstructionException Thrown if we cannot create an Event for any reason.
*/
public Event(boolean singlePlayer, int mapIndex) throws EventConstructionException {
public Event(boolean singlePlayer, int mapIndex, int raceLength) throws
EventConstructionException {
PolarParser.parseNewPolars("mock/polars/acc_polars.csv");
this.mapIndex = mapIndex;
String raceXMLFile;
@ -125,8 +114,8 @@ public class Event {
} else {
this.raceXML = Event.setRaceXMLAtCurrentTimeToNow(XMLReader.readXMLFileToString(raceXMLFile, StandardCharsets.UTF_8));
this.raceXML = RaceXMLCreator.alterRaceToWind(this.raceXML, XMLFileType.Contents, windAngle);
this.raceXML = RaceXMLCreator.scaleRaceSize(raceXML, windSpeed, 15 * 60 * 1000);
this.raceXML = RaceXMLCreator.scaleRaceSize(raceXML,
windSpeed, raceLength);
}
this.boatXML = XMLReader.readXMLFileToString(boatsXMLFile, StandardCharsets.UTF_8);

@ -27,7 +27,7 @@ public class MockBoat extends Boat {
* 1: passed only first check
* 2: passed first and second check
*/
private Integer roundingStatus = 0;
private int roundingStatus = 0;
/**
* Stores whether the boat is on autoVMG or not
@ -284,8 +284,8 @@ public class MockBoat extends Boat {
(this.isStarboardSide(mark1) && this.isPortSide(mark2));
}
public Integer getRoundingStatus() {
return Integer.valueOf(roundingStatus);
public int getRoundingStatus() {
return roundingStatus;
}
public void increaseRoundingStatus() {

@ -419,6 +419,11 @@ public class MockRace extends RaceState {
boat.setTimeSinceTackChange(boat.getTimeSinceTackChange() + updatePeriodMilliseconds);
}
// Remove one unit of health for every frame spent outside boundary
if(!finish && !GPSCoordinate.isInsideBoundary(boat.getPosition(), getBoundary())) {
boat.updateHealth(-0.1);
}
this.updateEstimatedTime(boat);
}
@ -430,7 +435,7 @@ public class MockRace extends RaceState {
boat.getBearing()
), boat.getBearing()) ;
if (vmg.getSpeed() > 0) {
boat.setCurrentSpeed(vmg.getSpeed());
boat.setCurrentSpeed(vmg.getSpeed() * Math.pow(boat.getHealth() / 100, 0.3));
}
}
@ -677,7 +682,7 @@ public class MockRace extends RaceState {
for (MockBoat boat : this.boats) {
//If the boat is currently racing, count it.
if (boat.getStatus() == BoatStatusEnum.RACING) {
if (boat.getStatus() == BoatStatusEnum.RACING && boat.getHealth()>=1) {
numberOfActiveBoats++;
}

@ -163,7 +163,7 @@ public class RaceLogic implements RunnableWithFramePeriod, Observer {
//As long as there is at least one boat racing, we still simulate the race.
if (race.getNumberOfActiveBoats() != 0) {
//System.out.println(race.getNumberOfActiveBoats());
//Get the time period of this frame.
long framePeriod = currentTime - previousFrameTime;
@ -175,12 +175,9 @@ public class RaceLogic implements RunnableWithFramePeriod, Observer {
race.updatePosition(boat, framePeriod, race.getRaceClock().getDurationMilli());
if(race.getColliderRegistry().rayCast(boat)){
//System.out.println("Collision!");
//Add boat to list
collisionBoats.add(boat);
}
//System.out.println(race.getColliderRegistry().rayCast(boat));
}

@ -64,6 +64,9 @@ public class RaceServer {
//Parse the boat locations.
snapshotMessages.addAll(parseBoatLocations());
//Parse the boat states
snapshotMessages.addAll(parseBoatStates());
//Parse the marks.
snapshotMessages.addAll(parseMarks());
@ -243,7 +246,29 @@ public class RaceServer {
}
/**
* Generates BoatState messages for every boat in the race
* @return list of BoatState messages
*/
private List<BoatState> parseBoatStates() {
List<BoatState> boatStates = new ArrayList<>();
for(MockBoat boat: race.getBoats()) {
boatStates.add(parseIndividualBoatState(boat));
}
return boatStates;
}
/**
* Creates a BoatState message for the current state of the given boat
* @param boat to generate message for
* @return BoatState message
*/
private BoatState parseIndividualBoatState(MockBoat boat) {
return new BoatState(
boat.getSourceID(),
(int)boat.getHealth()
);
}
/**
* Parses the race status, and returns it.

@ -21,6 +21,8 @@ import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Map;
/**
* Helper Class for creating a Race XML
@ -175,10 +177,10 @@ public class RaceXMLCreator {
}
/**
* gets the destance between two marks
* gets the destance between two marks, in nautical miles.
* @param a mark 1
* @param b mark 2
* @return
* @return Distance in nautical miles.
*/
private static double getDistance(XMLMark a, XMLMark b){
GPSCoordinate coorda = new GPSCoordinate(a.getTargetLat(), a.getTargetLng());
@ -212,12 +214,16 @@ public class RaceXMLCreator {
double averageSpeed = (bestDownWindSpeed + bestUpWindSpeed) / 2;
double raceApproximateTime = getRaceLength(race, averageSpeed);
double scale = milliseconds / raceApproximateTime;
Map<XMLCompoundMark, Boolean> hasBeenScaled = new HashMap<>();
for (XMLCorner cm: race.getCompoundMarkSequence().getCorner()){
int index = cm.getCompoundMarkID() - 1;
XMLCompoundMark mark = race.getCourse().getCompoundMark().get(index);
for (XMLMark m: mark.getMark()){
scalePoint(m, center, scale);
if (!hasBeenScaled.containsKey(mark)) {
for (XMLMark m : mark.getMark()) {
scalePoint(m, center, scale);
}
}
hasBeenScaled.put(mark, true);
}
for (XMLLimit limit: race.getCourseLimit().getLimit()){
scalePoint(limit, center, scale);
@ -319,8 +325,13 @@ public class RaceXMLCreator {
}
/**
* sets the current race time of the xml
* @param raceXML race xml to alter
* @param racePrestartTime prestart time
* @param racePreparatoryTime preparatory time
* @deprecated this should be used from the RaceXMLCreator not from this function
*/
public static void setRaceXMLAtCurrentTimeToNow(XMLRace raceXML, long racePrestartTime, long racePreparatoryTime){
//The start time is current time + 4 minutes. prestart is 3 minutes, and we add another minute.
long millisecondsToAdd = racePrestartTime + racePreparatoryTime;

@ -0,0 +1,38 @@
package network.MessageDecoders;
import network.Exceptions.InvalidMessageException;
import network.Messages.AC35Data;
import network.Messages.BoatState;
import java.util.Arrays;
import static network.Utils.ByteConverter.bytesToInt;
/**
* Decoder for {@link BoatState} messages
*/
public class BoatStateDecoder implements MessageDecoder {
/**
* Decoded BoatState message
*/
private BoatState message;
@Override
public AC35Data decode(byte[] encodedMessage) throws InvalidMessageException {
byte[] sourceID = Arrays.copyOfRange(encodedMessage, 0, 4);
byte boatHealth = encodedMessage[4];
// Unpack bytes into BoatState
this.message = new BoatState(
bytesToInt(sourceID),
boatHealth
);
// Return BoatState
return this.message;
}
public BoatState getMessage() {
return message;
}
}

@ -59,6 +59,7 @@ public class DecoderFactory {
case BOATACTION: return new BoatActionDecoder();
case BOATSTATE: return new BoatStateDecoder();
default: throw new InvalidMessageTypeException("Unrecognised message type: " + type);
}

@ -18,6 +18,5 @@ public interface MessageDecoder {
* @return The decoded message.
* @throws InvalidMessageException Thrown if the encoded message is invalid in some way, or cannot be decoded.
*/
public AC35Data decode(byte[] encodedMessage) throws InvalidMessageException;
AC35Data decode(byte[] encodedMessage) throws InvalidMessageException;
}

@ -0,0 +1,32 @@
package network.MessageEncoders;
import network.Exceptions.InvalidMessageException;
import network.Messages.AC35Data;
import network.Messages.BoatState;
import java.nio.ByteBuffer;
import static network.Utils.ByteConverter.intToBytes;
/**
* Encoder for {@link BoatState} message
*/
public class BoatStateEncoder implements MessageEncoder {
@Override
public byte[] encode(AC35Data message) throws InvalidMessageException {
// Downcast message
BoatState boatState = (BoatState)message;
//Serialise message
byte[] sourceID = intToBytes(boatState.getSourceID());
byte boatHealth = (byte)boatState.getBoatHealth();
// Pack bytes into string
ByteBuffer boatStateMessage = ByteBuffer.allocate(5);
boatStateMessage.put(sourceID);
boatStateMessage.put(boatHealth);
// Return byte string
return boatStateMessage.array();
}
}

@ -59,6 +59,7 @@ public class EncoderFactory {
case BOATACTION: return new BoatActionEncoder();
case BOATSTATE: return new BoatStateEncoder();
default: throw new InvalidMessageTypeException("Unrecognised message type: " + type);
}

@ -0,0 +1,31 @@
package network.Messages;
import network.Messages.Enums.MessageType;
/**
* Represents the information in a BoatState message according to protocol meeting
*/
public class BoatState extends AC35Data {
/**
* Source ID of boat described in message
*/
private int sourceID;
/**
* Health between 0-100 of boat with above source ID
*/
private int boatHealth;
public BoatState(int sourceID, int boatHealth) {
super(MessageType.BOATSTATE);
this.sourceID = sourceID;
this.boatHealth = boatHealth;
}
public int getSourceID() {
return sourceID;
}
public int getBoatHealth() {
return boatHealth;
}
}

@ -43,6 +43,8 @@ public enum MessageType {
HOSTED_GAMES_REQUEST(109),
BOATSTATE(103),
NOTAMESSAGE(0);
@ -56,7 +58,7 @@ public enum MessageType {
* Creates a MessageType enum from a given primitive integer value, cast to a byte.
* @param value Integer, which is cast to byte, to construct from.
*/
private MessageType(int value) {
MessageType(int value) {
this.value = (byte)value;
}

@ -103,6 +103,11 @@ public class Boat extends Collider {
*/
private boolean isColliding = false;
/**
* Amount of health boat currently has, between 0 and 100
*/
private double health;
/**
* Constructs a boat object with a given sourceID, name, country/team abbreviation, and polars table.
*
@ -113,6 +118,7 @@ public class Boat extends Collider {
public Boat(int sourceID, String name, String country) {
this.sourceID = sourceID;
this.health = 100;
this.setName(name);
this.setCountry(country);
@ -419,6 +425,8 @@ public class Boat extends Collider {
@Override
public void onCollisionEnter(Collision e) {
if(e.getBearing().degrees() > 270 || e.getBearing().degrees() < 90) {
// Deplete health
e.getBoat().updateHealth(-5);
// Notify observers of collision
this.setChanged();
notifyObservers(e);
@ -432,4 +440,22 @@ public class Boat extends Collider {
public void setColliding(boolean colliding) {
isColliding = colliding;
}
public double getHealth() {
return health;
}
public void setHealth(double health) {
this.health = health;
}
/**
* Add a given amount of HP to boat health
* @param delta amount of HP to add
*/
public void updateHealth(double delta) {
health += delta;
if(health < 0) health = 0;
else if(health > 100) health = 100;
}
}

@ -97,6 +97,7 @@ public class Mark extends Collider{
@Override
public void onCollisionEnter(Collision e) {
e.getBoat().updateHealth(-10);
this.setChanged();
notifyObservers(e);
}

@ -0,0 +1,30 @@
package visualiser.Commands.VisualiserRaceCommands;
import mock.model.commandFactory.Command;
import network.Messages.BoatState;
import shared.exceptions.BoatNotFoundException;
import visualiser.model.VisualiserBoat;
import visualiser.model.VisualiserRaceState;
/**
* Updates boats on visualiser when their health changes
*/
public class BoatStateCommand implements Command {
private BoatState boatState;
private VisualiserRaceState visualiserRace;
public BoatStateCommand(BoatState boatState, VisualiserRaceState visualiserRace) {
this.boatState = boatState;
this.visualiserRace = visualiserRace;
}
@Override
public void execute() {
try {
VisualiserBoat boat = visualiserRace.getBoat(boatState.getSourceID());
boat.setHealth(boatState.getBoatHealth());
} catch (BoatNotFoundException e) {
// Fail silently
}
}
}

@ -24,7 +24,6 @@ public class VisualiserRaceCommandFactory {
switch (message.getType()) {
case BOATLOCATION:
//System.out.println("Boat location received");
return new BoatLocationCommand((BoatLocation) message, visualiserRace);
case RACESTATUS: return new RaceStatusCommand((RaceStatus) message, visualiserRace);
@ -36,7 +35,8 @@ public class VisualiserRaceCommandFactory {
case YACHTEVENTCODE:
return new BoatCollisionCommand((YachtEvent) message, visualiserRace);
case BOATSTATE:
return new BoatStateCommand((BoatState) message, visualiserRace);
default: throw new CommandConstructionException("Could not create VisualiserRaceCommand. Unrecognised or unsupported MessageType: " + message.getType());

@ -4,6 +4,8 @@ import javafx.application.Platform;
import javafx.fxml.FXML;
import javafx.scene.control.Alert;
import javafx.scene.control.ButtonType;
import javafx.scene.control.Label;
import javafx.scene.control.Slider;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import mock.app.Event;
@ -28,8 +30,12 @@ import java.util.logging.Logger;
*/
public class HostGameController extends Controller {
private @FXML ImageView mapImage;
private @FXML Slider sliderLength;
private @FXML Label lblLength;
private ArrayList<Image> listOfMaps;
private int currentMapIndex = 0;
private int selectedRaceLength; // in minutes
private final int MAX_RACE_LENGTH = 30; // in minutes
private DatagramSocket udpSocket;
private MatchBrowserInterface matchBrowserInterface;
@ -37,8 +43,33 @@ public class HostGameController extends Controller {
loadMaps();
this.udpSocket = MatchBrowserSingleton.getInstance().getUdpSocket();
this.matchBrowserInterface = MatchBrowserSingleton.getInstance().getMatchBrowserInterface();
setRaceLengthSlider();
}
/**
* Sets up the values and display for a slider object which allows a user
* to select how many minutes long they would like their race to be.
*/
private void setRaceLengthSlider(){
// set the listener to update the label
sliderLength.valueProperty().addListener((ov, old_val, new_val) -> {
selectedRaceLength = new_val.intValue();
if (selectedRaceLength == 1){
lblLength.setText(selectedRaceLength + " minute.");
} else {
lblLength.setText(selectedRaceLength + " minutes.");
}
});
// set values and marks to be displayed
sliderLength.setMin(2);
sliderLength.setMax(MAX_RACE_LENGTH);
sliderLength.setShowTickLabels(true);
sliderLength.setMajorTickUnit(MAX_RACE_LENGTH-1);
sliderLength.setBlockIncrement(1);
sliderLength.getStylesheets().add("/css/slider.css");
}
/**
@ -64,7 +95,8 @@ public class HostGameController extends Controller {
*/
public void hostGamePressed() {
try {
App.game = new Event(false, currentMapIndex);
App.game = new Event(false, currentMapIndex,
selectedRaceLength*60*1000);
App.gameType = currentMapIndex;
HttpMatchBrowserHost matchBrowserHost = new HttpMatchBrowserHost();

@ -40,7 +40,7 @@ public class LobbyController extends Controller {
private @FXML TextField addressFld;
private @FXML TextField portFld;
private ObservableList<RaceConnection> connections;
private ObservableList<RaceConnection> allConnections;
private ObservableList<RaceConnection> customConnections;
private AudioClip sound;
@ -51,12 +51,20 @@ public class LobbyController extends Controller {
public void initialize() {
httpMatchBrowserClient = new HttpMatchBrowserClient();
httpMatchBrowserClient.connections.addListener(new ListChangeListener<RaceConnection>() {
@Override
public void onChanged(Change<? extends RaceConnection> c) {
refreshTable();
}
});
new Thread(httpMatchBrowserClient, "Match Client").start();
// set up the connection table
customConnections = FXCollections.observableArrayList();
allConnections = FXCollections.observableArrayList();
//connections.add(new RaceConnection("localhost", 4942, "Local Game"));
lobbyTable.setItems(httpMatchBrowserClient.connections);
lobbyTable.setItems(allConnections);
gameNameColumn.setCellValueFactory(cellData -> cellData.getValue().gamenameProperty());
hostNameColumn.setCellValueFactory(cellData -> cellData.getValue().hostnameProperty());
statusColumn.setCellValueFactory(cellData -> cellData.getValue().statusProperty());
@ -82,8 +90,14 @@ public class LobbyController extends Controller {
public void refreshBtnPressed(){
sound = new AudioClip(this.getClass().getResource("/visualiser/sounds/buttonpress.wav").toExternalForm());
sound.play();
refreshTable();
}
private void refreshTable() {
allConnections.clear();
addCustomGames();
addServerGames();
for(RaceConnection connection: connections) {
for(RaceConnection connection: allConnections) {
connection.check();
}
try {
@ -144,9 +158,9 @@ public class LobbyController extends Controller {
try {
int port = Integer.parseInt(portString);
customConnections.add(new RaceConnection(hostName, port, "Boat Game"));
connections.addAll(customConnections);
addressFld.clear();
portFld.clear();
refreshTable();
} catch (NumberFormatException e) {
System.err.println("Port number entered is not a number");
}
@ -173,17 +187,14 @@ public class LobbyController extends Controller {
* Adds the games received from the server
*/
private void addServerGames() {
httpMatchBrowserClient.connections.addAll(customConnections);
httpMatchBrowserClient.connections.addListener(new ListChangeListener<RaceConnection>() {
@Override
public void onChanged(Change<? extends RaceConnection> c) {
refreshBtnPressed();
}
});
allConnections.addAll(httpMatchBrowserClient.connections);
/*
for (HostGame game : matchBrowserLobbyInterface.getGames()) {
connections.add(new RaceConnection(game.getIp(), 4942, "Boat Game"));
}*/
}
private void addCustomGames() {
allConnections.addAll(customConnections);
}
}

@ -0,0 +1,123 @@
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.geometry.Point3D;
import javafx.scene.AmbientLight;
import javafx.scene.control.Label;
import javafx.scene.layout.Pane;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.scene.paint.PhongMaterial;
import javafx.scene.shape.Cylinder;
import javafx.scene.shape.MeshView;
import javafx.scene.shape.Shape3D;
import javafx.scene.transform.Rotate;
import shared.model.Bearing;
import shared.model.CompoundMark;
import shared.model.GPSCoordinate;
import visualiser.layout.Annotation3D;
import visualiser.layout.Assets3D;
import visualiser.layout.Subject3D;
import visualiser.layout.View3D;
import visualiser.model.VisualiserBoat;
import java.net.URL;
import java.util.Observable;
import java.util.Observer;
public class NextMarkController {
private @FXML StackPane arrowStackPane2d;
private @FXML StackPane arrowStackPane3d;
private @FXML Pane pane2d;
private @FXML Pane pane3d;
private VisualiserBoat boat;
public void initialiseArrowView(VisualiserBoat boat) {
this.boat = boat;
pane2d.setVisible(true);
pane3d.setVisible(false);
initialise2dArrowView();
initialise3dArrowView();
}
private void initialise2dArrowView() {
AnimationTimer arrow2d = new AnimationTimer() {
@Override
public void handle(long now) {
if (boat.getCurrentLeg().getEndCompoundMark() != null) {
CompoundMark target = boat.getCurrentLeg().getEndCompoundMark();
Bearing headingToMark = GPSCoordinate.calculateBearing(boat.getPosition(), target.getAverageGPSCoordinate());
arrowStackPane2d.setRotate(headingToMark.degrees());
} else {
stop();
}
}
};
arrow2d.start();
}
private void initialise3dArrowView() {
ObservableList<Subject3D> viewSubjects = FXCollections.observableArrayList();
String arrowPath = "assets/mark_arrow.x3d";
Shape3D arrow = Assets3D.loadX3d(arrowPath);
arrow.setScaleX(25);
arrow.setScaleY(25);
arrow.setScaleZ(100);
arrow.setRotationAxis(new Point3D(1,0,0));
arrowStackPane3d.getChildren().add(arrow);
AnimationTimer arrow3d = new AnimationTimer() {
@Override
public void handle(long now) {
if (boat.getCurrentLeg().getEndCompoundMark() != null) {
arrow.getTransforms().clear();
double zRotation = calculateZRotate();
arrow.setRotate(calculateXRotate(zRotation));
arrow.getTransforms().add(new Rotate(zRotation, new Point3D(0, 0, 1)));
} else {
stop();
}
}
};
arrow3d.start();
}
public void show2d() {
pane3d.setVisible(false);
pane2d.setVisible(true);
}
public void show3d() {
pane2d.setVisible(false);
pane3d.setVisible(true);
}
private double calculateZRotate() {
CompoundMark target = boat.getCurrentLeg().getEndCompoundMark();
Bearing headingToMark = GPSCoordinate.calculateBearing(boat.getPosition(), target.getAverageGPSCoordinate());
return -headingToMark.degrees() + boat.getBearing().degrees() + 180;
}
private double calculateXRotate(double zRotation) {
// if (zRotation > 360) {
// zRotation -=360;
// } else if (zRotation < 0) {
// zRotation += 360;
// }
//
// if (zRotation > 180) {
// zRotation = 360 - zRotation;
// }
return 70;
//return 90 - 20 * Math.cos(Math.toRadians(zRotation));
}
}

@ -15,19 +15,25 @@ import javafx.scene.AmbientLight;
import javafx.scene.PointLight;
import javafx.scene.chart.LineChart;
import javafx.scene.control.*;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.Pane;
import javafx.scene.layout.StackPane;
import javafx.scene.media.AudioClip;
import javafx.scene.paint.Color;
import javafx.scene.paint.Material;
import javafx.scene.paint.PhongMaterial;
import javafx.scene.shape.Cylinder;
import javafx.scene.paint.Stop;
import javafx.scene.shape.MeshView;
import javafx.scene.shape.Shape3D;
import javafx.scene.transform.Translate;
import javafx.util.Callback;
import network.Messages.Enums.BoatStatusEnum;
import network.Messages.Enums.RaceStatusEnum;
import shared.dataInput.RaceDataSource;
import shared.enums.RoundingType;
@ -43,6 +49,7 @@ import visualiser.model.*;
import visualiser.utils.GPSConverter;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.*;
import java.util.logging.Level;
@ -72,6 +79,8 @@ public class RaceViewController extends Controller {
private ResizableRaceCanvas raceCanvas;
private boolean mapToggle = true;
private GPSConverter gpsConverter;
private ArrayList<HealthEffect> healthEffectList = new ArrayList<>();
/**
* Arrow pointing to next mark in third person
@ -84,10 +93,12 @@ public class RaceViewController extends Controller {
// note: it says it's not used but it is! do not remove :)
private @FXML ArrowController arrowController;
private @FXML NextMarkController nextMarkController;
private @FXML GridPane canvasBase;
private @FXML GridPane canvasBase1;
private @FXML SplitPane racePane;
private @FXML StackPane arrowPane;
private @FXML Pane nextMarkPane;
private @FXML Label timer;
private @FXML Label FPS;
private @FXML Label timeZone;
@ -97,12 +108,17 @@ public class RaceViewController extends Controller {
private @FXML TableColumn<VisualiserBoat, String> boatTeamColumn;
private @FXML TableColumn<VisualiserBoat, Leg> boatMarkColumn;
private @FXML TableColumn<VisualiserBoat, Number> boatSpeedColumn;
private @FXML TableColumn<VisualiserBoat, Number> boatHealthColumn;
private @FXML LineChart<Number, Number> sparklineChart;
private @FXML Label tutorialText;
private @FXML AnchorPane infoWrapper;
private @FXML AnchorPane lineChartWrapper;
private @FXML StackPane speedPane;
private @FXML AnchorPane raceAnchorPane;
private @FXML GridPane playerHealthContainer;
private @FXML ImageView imageView;
private @FXML AnchorPane deathTransPane;
private @FXML StackPane deathPane;
/**
* Displays a specified race.
@ -117,11 +133,13 @@ public class RaceViewController extends Controller {
this.controllerClient = controllerClient;
this.isHost = isHost;
keyFactory.load();
deathPane.setDisable(false);
deathPane.setVisible(false);
tutorialCheck();
initKeypressHandler();
initialiseRaceVisuals();
initialiseRaceCanvas();
healthLoop();
}
/**
@ -133,7 +151,6 @@ public class RaceViewController extends Controller {
isTutorial = true;
tutorialText.setVisible(true);
tutorialStates = new ArrayList<>(Arrays.asList(TutorialState.values()));
currentState = tutorialStates.get(0);
tutorialStates.remove(0);
searchMapForKey("Upwind");
@ -290,33 +307,47 @@ public class RaceViewController extends Controller {
* Initialises the various UI components to listen to the {@link #visualiserRace}.
*/
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
initialiseFps();
initialiseInfoTable();
initialiseView3D(this.visualiserRace);
initialiseHealthPane();
initialiseRaceClock();
initialiseSpeedometer();
raceTimer(); // start the timer
nextMarkPane.toFront();
speedometerLoop();
new Sparkline(this.raceState, this.sparklineChart);
timeZone.setText(this.raceState.getRaceClock().getTimeZone());
arrowController.setWindProperty(this.raceState.windProperty());
}
private void initialiseHealthPane() {
InputStream tomato = this.getClass().getClassLoader().getResourceAsStream("visualiser/images/tomato.png");
HealthSlider healthSlider = new HealthSlider(new Image(tomato));
playerHealthContainer.add(healthSlider, 0, 0);
try {
VisualiserBoat player = raceState.getBoat(raceState.getPlayerBoatID());
player.healthProperty().addListener((o, prev, curr) -> {
healthSlider.setCrop((double)curr/100.0);
});
} catch (BoatNotFoundException e) {
e.printStackTrace();
}
}
private void initialiseView3D(VisualiserRaceEvent race) {
viewSubjects = FXCollections.observableArrayList();
try {
nextMarkController.initialiseArrowView(race.getVisualiserRaceState().getBoat(race.getVisualiserRaceState().getPlayerBoatID()));
} catch (BoatNotFoundException e) {
e.printStackTrace();
}
AmbientLight ambientLight = new AmbientLight(Color.web("#CCCCFF"));
ambientLight.setTranslateX(250);
ambientLight.setTranslateZ(210);
@ -406,11 +437,29 @@ public class RaceViewController extends Controller {
viewSubjects.add(markModel);
}
for (VisualiserBoat boat: race.getVisualiserRaceState().getBoats()) {
// Position and add each boat to view
for(VisualiserBoat boat: race.getVisualiserRaceState().getBoats()) {
Shape3D mesh = Assets3D.getBoat();
PhongMaterial boatColorMat = new PhongMaterial(boat.getColor());
//mesh.setMaterial(boatColorMat);
Subject3D boatModel = new Subject3D(mesh, boat.getSourceID());
// Configure visualiser for client's boat
if (boat.isClientBoat()) {
// Add player boat highlight
Shockwave boatHighlight = new Shockwave(10);
boatHighlight.getMesh().setMaterial(new PhongMaterial(new Color(1, 1, 0, 0.1)));
viewSubjects.add(boatHighlight);
// Track player boat with camera
viewSubjects.add(boatModel);
Platform.runLater(() -> {
view3D.trackSubject(boatModel);
view3D.setThirdPerson();
});
// Track player boat with highlight
AnimationTimer highlightTrack = new AnimationTimer() {
@Override
public void handle(long now) {
@ -419,28 +468,28 @@ public class RaceViewController extends Controller {
}
};
highlightTrack.start();
}
}
// Position and add each boat to view
for(VisualiserBoat boat: race.getVisualiserRaceState().getBoats()) {
// MeshView mesh;
// if(boat.getSourceID() == race.getVisualiserRaceState().getPlayerBoatID()) {
// mesh = new MeshView(importer.getImport());
// } else {
// mesh = new MeshView(importerBurgerBoat.getImport());
// }
Shape3D mesh = Assets3D.getBoat();
PhongMaterial boatColorMat = new PhongMaterial(boat.getColor());
//mesh.setMaterial(boatColorMat);
Subject3D boatModel = new Subject3D(mesh, boat.getSourceID());
// Highlight next mark only for player boat
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);
}
boat.legProperty().addListener((o, prev, curr) -> Platform.runLater(() -> swapColours(curr)));
} else {
viewSubjects.add(boatModel);
}
viewSubjects.add(boatModel);
//Create health effect
HealthEffect healthEffect = new HealthEffect(boat.getSourceID(), System.currentTimeMillis());
viewSubjects.add(healthEffect);
healthEffectList.add(healthEffect);
//add sail
Sails3D sails3D = new Sails3D();
Subject3D sailsSubject = new Subject3D(sails3D, 0);
sails3D.setMouseTransparent(true);
sails3D.setMaterial(boatColorMat);
sailsSubject.setXRot(0d);
sailsSubject.setHeading(visualiserRace.getVisualiserRaceState().getWindDirection().degrees());
@ -517,6 +566,14 @@ public class RaceViewController extends Controller {
boatModel.setHeading(boat.getBearing().degrees());
boatModel.setX(gpsConverter.convertGPS(boat.getPosition()).getX());
boatModel.setZ(gpsConverter.convertGPS(boat.getPosition()).getY());
boatModel.getMesh().toFront();
//Fire follows boat
healthEffect.setHeading(boat.getBearing().degrees());
healthEffect.setX(gpsConverter.convertGPS(boat.getPosition()).getX());
healthEffect.setZ(gpsConverter.convertGPS(boat.getPosition()).getY());
healthEffect.setY(0);
}
};
trackBoat.start();
@ -543,7 +600,6 @@ public class RaceViewController extends Controller {
}*/
Subject3D shockwave = new Shockwave(10);
viewSubjects.add(shockwave);
//boat.legProperty().addListener((o, prev, curr) -> Platform.runLater(() -> swapColours(curr)));
@ -632,37 +688,32 @@ public class RaceViewController extends Controller {
}
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();
if (target != null) {
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();
nextMarkController.show3d();
// 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();
}
nextMarkController.show2d();
}
/**
@ -789,13 +840,17 @@ public class RaceViewController extends Controller {
// set table data
boatInfoTable.setItems(sortedBoats);
boatTeamColumn.setCellValueFactory(
cellData -> cellData.getValue().nameProperty());
cellData -> cellData.getValue().nameProperty()
);
boatSpeedColumn.setCellValueFactory(
cellData -> cellData.getValue().currentSpeedProperty());
cellData -> cellData.getValue().currentSpeedProperty()
);
boatMarkColumn.setCellValueFactory(
cellData -> cellData.getValue().legProperty());
boatPlacingColumn.setCellValueFactory(
cellData -> cellData.getValue().placingProperty());
cellData -> cellData.getValue().legProperty()
);
boatHealthColumn.setCellValueFactory(
cellData -> cellData.getValue().healthProperty()
);
//Kind of ugly, but allows for formatting an observed speed.
boatSpeedColumn.setCellFactory(
@ -927,6 +982,64 @@ public class RaceViewController extends Controller {
}.start();
}
/**
* Animation timer for health
*/
private void healthLoop(){
new AnimationTimer(){
@Override
public void handle(long arg0){
if (raceState.getRaceStatusEnum() == RaceStatusEnum.FINISHED) {
stop(); // stop the timer
} else {
try {
//Check if boat is dead
if(raceState.getBoat(raceState.getPlayerBoatID()).getHealth()<=0){
if(!deathPane.isDisable()) {
deathPane.setVisible(true);
}
}
} catch (BoatNotFoundException e) {
e.printStackTrace();
}
for(VisualiserBoat boat : raceState.getBoats()){
for (HealthEffect fp : healthEffectList){
if(fp.getSourceID()==boat.getSourceID()){
if(boat.getHealth()<=0){
//Boat is dead. Don't check it anymore for hp
fp.displayDeath(fp.getSourceID()==raceState.getPlayerBoatID());
fp.setSourceID(0);
try {
raceState.getBoat(boat.getSourceID()).setStatus(BoatStatusEnum.DNF);
raceState.updateBoatPositions(raceState.getBoats());
} catch (BoatNotFoundException e) {
e.printStackTrace();
}
}
else
//Speed up tick when <=10 hp
if(boat.getHealth()<=10){
fp.flash(System.currentTimeMillis(), 300, boat.getSourceID()==raceState.getPlayerBoatID());
}
else
//Visual indication of low hp
if(boat.getHealth()<=20) {
//fp.setVisible(true);
fp.flash(System.currentTimeMillis(), 500, boat.getSourceID()==raceState.getPlayerBoatID());
} else {
fp.setVisible(false);
}
break;
}
}
}
}
}
}.start();
}
/**
* toggles if the info table is shown
@ -1082,4 +1195,12 @@ public class RaceViewController extends Controller {
mapToggle = !mapToggle;
}
/**
* FXML method for death button
*/
public void deathOKPressed(){
deathPane.setDisable(true);
deathPane.setVisible(false);
}
}

@ -104,7 +104,7 @@ public class Assets3D {
windArrow = new Annotation3D(loadX3d(arrowPath));
}
private static Shape3D loadX3d(String path){
public static Shape3D loadX3d(String path){
X3dModelImporter x3dModelImporter = new X3dModelImporter();
URL asset = Assets3D.class.getClassLoader().getResource(path);
x3dModelImporter.read(asset);

@ -0,0 +1,111 @@
package visualiser.layout;
import javafx.geometry.Point3D;
import javafx.scene.image.Image;
import javafx.scene.media.AudioClip;
import javafx.scene.paint.Color;
import javafx.scene.paint.PhongMaterial;
import javafx.scene.shape.*;
import javafx.scene.transform.Rotate;
/**
* Created by zwu18 on 24/09/17.
*/
public class HealthEffect extends Subject3D {
private int sourceID;
private long currentTime;
private AudioClip warningSound = new AudioClip(this.getClass().getResource("/visualiser/sounds/warning.mp3").toExternalForm());
private AudioClip deadSound = new AudioClip(this.getClass().getResource("/visualiser/sounds/dead1.wav").toExternalForm());
public HealthEffect(int sourceID, long currentTime){
super(createEffect(), 0);
this.sourceID = sourceID;
this.currentTime = currentTime;
}
/**
* Initialise the mesh view with image
* @return Mesh view
*/
private static Shape3D createEffect(){
Image image = new Image(HealthEffect.class.getClassLoader().getResourceAsStream("images/warning.png"));
Plane3D plane = new Plane3D(20, 20, 10, 10);
PhongMaterial material = new PhongMaterial();
material.setDiffuseColor(Color.web("#FFFFFF"));
material.setSpecularColor(Color.web("#000000"));
material.setDiffuseMap(image);
MeshView imageSurface = new MeshView(plane);
imageSurface.setMaterial(material);
imageSurface.setMouseTransparent(true);
//imageSurface.toFront(); this.flashInterval = flashInterval;
return imageSurface;
}
public void rotateView(Double angle, Double pivotX, Double pivotY, Double pivotZ, Point3D axis){
Rotate rotate = new Rotate(angle, axis);
rotate.setPivotX(pivotX);
rotate.setPivotY(pivotY);
rotate.setPivotZ(pivotZ);
this.getMesh().getTransforms().add(rotate);
}
public void setVisible(boolean bool){
this.getMesh().setVisible(bool);
}
public int getSourceID(){
return sourceID;
}
public void setSourceID(int id){
this.sourceID = id;
}
/**
* Display visual indication when boat dies
* @param player boolean if player is current user or not
*/
public void displayDeath(boolean player){
Image image = new Image(HealthEffect.class.getClassLoader().getResourceAsStream("images/warning2.png"));
PhongMaterial material = (PhongMaterial) this.getMesh().getMaterial();
material.setDiffuseColor(Color.web("#FFFFFF"));
material.setSpecularColor(Color.web("#000000"));
material.setDiffuseMap(image);
this.getMesh().setMaterial(material);
if(player) {
deadSound.play();
}
}
/**
* Flash the mesh view at a certain interval
* @param checkTime The current time of flash
* @param flashInterval Desired flash interval
* @param playerBoat Whether or not this effect is for the player's boat.
*/
public void flash(long checkTime, long flashInterval, boolean playerBoat){
if(checkTime >= (currentTime+flashInterval)){
this.currentTime = checkTime;
if(this.getMesh().isVisible()){
this.setVisible(false);
} else {
if(playerBoat) {
warningSound.setVolume(0.1);
warningSound.play();
}
this.setVisible(true);
}
}
}
}

@ -0,0 +1,66 @@
package visualiser.layout;
import javafx.scene.image.Image;
import javafx.scene.image.WritableImage;
import javafx.scene.layout.*;
import javafx.scene.shape.Rectangle;
import java.awt.image.BufferedImage;
/**
* Created by connortaylorbrown on 21/09/17.
*/
public class HealthSlider extends Pane {
/**
* Image used to fill health slider
*/
private Image fillImage;
/**
* Size of background for image configuration
*/
private BackgroundSize backgroundSize;
/**
* Percentage of image cropped out from top
*/
private double crop;
public HealthSlider(Image fillImage) {
this.fillImage = fillImage;
this.crop = 1;
this.backgroundSize = new BackgroundSize(
100,
100,
true,
true,
true,
false);
drawSlider();
}
public void setCrop(double crop) {
this.crop = crop;
drawSlider();
}
private void drawSlider() {
int top = Math.max(0,(int)(fillImage.getHeight() - crop * fillImage.getHeight()));
WritableImage croppedImage = new WritableImage(
fillImage.getPixelReader(),
0,
top,
(int)fillImage.getWidth(),
(int)fillImage.getHeight() - top
);
BackgroundImage backgroundImage = new BackgroundImage(
croppedImage,
BackgroundRepeat.NO_REPEAT,
BackgroundRepeat.NO_REPEAT,
BackgroundPosition.CENTER,
backgroundSize
);
this.setBackground(new Background(backgroundImage));
}
}

@ -81,7 +81,7 @@ public class View3D extends Pane {
/**
* Distance to stop zoom
*/
private final double ZOOM_IN_LIMIT = 30;
private final double ZOOM_IN_LIMIT = 3;
private final double ZOOM_OUT_LIMIT = 700;
private final double MAX_ZOOM_LIMIT = 1500;
private final double MAX_PITCH = 60; // birds eye view

@ -1,9 +1,6 @@
package visualiser.model;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.*;
import javafx.scene.paint.Color;
import network.Messages.Enums.BoatStatusEnum;
import shared.model.*;
@ -65,6 +62,7 @@ public class VisualiserBoat extends Boat {
private ObjectProperty<GPSCoordinate> positionProperty;
private ObjectProperty<Bearing> bearingProperty;
private BooleanProperty hasCollided;
private DoubleProperty healthProperty;
/**
@ -78,6 +76,7 @@ public class VisualiserBoat extends Boat {
this.color = color;
this.hasCollided = new SimpleBooleanProperty(false);
this.healthProperty = new SimpleDoubleProperty(100);
}
@ -284,4 +283,18 @@ public class VisualiserBoat extends Boat {
public void setHasCollided(boolean hasCollided) {
this.hasCollided.set(hasCollided);
}
public DoubleProperty healthProperty() {
return healthProperty;
}
@Override
public double getHealth() {
return healthProperty.get();
}
@Override
public void setHealth(double healthProperty) {
this.healthProperty.set((int)healthProperty);
}
}

@ -248,7 +248,6 @@ public class VisualiserRaceState extends RaceState {
if ((boat.getStatus() == BoatStatusEnum.DNF) || (boat.getStatus() == BoatStatusEnum.PRESTART) || (boat.getCurrentLeg().getLegNumber() < 0)) {
boat.setPlacing("-");
} else {

@ -297,6 +297,7 @@ public class ServerConnection implements RunnableWithFramePeriod {
this.messageRouter.addRoute(MessageType.MARKROUNDING, incomingMessages);
this.messageRouter.addRoute(MessageType.XMLMESSAGE, incomingMessages);
this.messageRouter.addRoute(MessageType.ASSIGN_PLAYER_BOAT, incomingMessages);
this.messageRouter.addRoute(MessageType.BOATSTATE, incomingMessages);
this.messageRouter.removeDefaultRoute(); //We no longer want to keep un-routed messages.

@ -0,0 +1,62 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE X3D PUBLIC "ISO//Web3D//DTD X3D 3.0//EN" "http://www.web3d.org/specifications/x3d-3.0.dtd">
<X3D version="3.0" profile="Immersive" xmlns:xsd="http://www.w3.org/2001/XMLSchema-instance" xsd:noNamespaceSchemaLocation="http://www.web3d.org/specifications/x3d-3.0.xsd">
<head>
<meta name="filename" content="Mark Arrow V1.3.x3d" />
<meta name="generator" content="Blender 2.77 (sub 0)" />
</head>
<Scene>
<NavigationInfo headlight="true"
visibilityLimit="0.0"
type='"EXAMINE", "ANY"'
avatarSize="0.25, 1.75, 0.75"
/>
<Background DEF="WO_World"
groundColor="0.051 0.051 0.051"
skyColor="0.051 0.051 0.051"
/>
<Transform DEF="Cube_TRANSFORM"
translation="0.000000 0.000000 0.000000"
scale="1.000000 1.000000 1.000000"
rotation="0.000000 0.707107 0.707107 3.141593"
>
<Transform DEF="Cube_ifs_TRANSFORM"
translation="0.000000 0.000000 0.000000"
scale="1.000000 1.000000 1.000000"
rotation="1.000000 0.000000 0.000000 0.000000"
>
<Group DEF="group_ME_Cube">
<Shape>
<Appearance>
<ImageTexture DEF="IM_Material_Diffuse_Color_002"
url='"textures/Material Diffuse Color.002" "Material Diffuse Color.002" "D:/Storage/3D Models/Misc/SENG302/Mark Arrow/textures/Material Diffuse Color.002"'
/>
<TextureTransform
translation="0.000000 0.000000"
scale="1.000000 1.000000"
rotation="0.000000"
/>
<Material DEF="MA_Material"
diffuseColor="0.800 0.800 0.800"
specularColor="0.401 0.401 0.401"
emissiveColor="0.000 0.000 0.000"
ambientIntensity="0.333"
shininess="0.098"
transparency="0.0"
/>
</Appearance>
<IndexedFaceSet solid="true"
texCoordIndex="0 1 2 -1 3 4 5 -1 6 7 8 9 -1 10 11 12 -1 13 14 15 -1 16 17 18 19 -1 20 21 22 -1 23 24 25 -1 26 27 28 29 -1 30 31 32 -1 33 34 35 -1 36 37 38 39 -1 "
coordIndex="4 1 7 -1 2 0 8 -1 1 0 2 3 -1 1 3 7 -1 8 0 9 -1 0 1 4 9 -1 4 7 6 -1 2 8 5 -1 6 3 2 5 -1 6 7 3 -1 8 9 5 -1 4 6 5 9 -1 "
>
<Coordinate DEF="coords_ME_Cube"
point="1.000000 -0.000000 -0.100000 1.000000 -0.000001 0.100000 0.000000 2.000000 -0.100000 0.000000 2.000000 0.100000 0.000000 -2.000000 0.100000 -1.000000 -0.000000 -0.100000 -1.000000 -0.000001 0.100000 0.000000 -0.000000 0.100000 0.000000 0.000000 -0.100000 0.000000 -2.000000 -0.100000 "
/>
<TextureCoordinate point="0.9999 0.0001 0.7826 0.0001 0.8260 0.0870 0.6522 0.1934 0.7826 0.3673 0.8260 0.2803 0.4348 0.1740 0.4348 0.1934 0.6522 0.1934 0.6522 0.1740 0.7826 0.0001 0.6522 0.1740 0.8260 0.0870 0.8260 0.2803 0.7826 0.3673 0.9999 0.3673 0.4348 0.1934 0.4348 0.1740 0.2174 0.1740 0.2174 0.1934 0.9999 0.0001 0.8260 0.0870 0.8695 0.1740 0.6522 0.1934 0.8260 0.2803 0.8695 0.1934 0.8695 0.1740 0.6522 0.1740 0.6522 0.1934 0.8695 0.1934 0.8695 0.1740 0.8260 0.0870 0.6522 0.1740 0.8260 0.2803 0.9999 0.3673 0.8695 0.1934 0.2174 0.1740 0.0001 0.1740 0.0001 0.1934 0.2174 0.1934 " />
</IndexedFaceSet>
</Shape>
</Group>
</Transform>
</Transform>
</Scene>
</X3D>

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

@ -0,0 +1,16 @@
.slider .thumb {
-fx-background-image: url("/visualiser/images/sun.png");
-fx-background-size: 25px;
-fx-pref-height: 25px;
-fx-pref-width: 25px;
-fx-background-position: top;
-fx-background-repeat: no-repeat;
-fx-focus-color: transparent;
-fx-background-color: transparent;
}
.slider .track {
-fx-control-inner-background: dodgerblue;
-fx-border-color: rgba(30, 144, 255, 0.44);
-fx-border-radius: 0.25em, 0.25em, 0.166667em;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

@ -3,6 +3,7 @@
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.Slider?>
<?import javafx.scene.image.ImageView?>
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.layout.ColumnConstraints?>
@ -12,19 +13,20 @@
<AnchorPane fx:id="hostWrapper" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="600.0" prefWidth="780.0" xmlns="http://javafx.com/javafx/8.0.111" xmlns:fx="http://javafx.com/fxml/1" fx:controller="visualiser.Controllers.HostGameController">
<children>
<GridPane layoutY="14.0" AnchorPane.bottomAnchor="-14.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="14.0">
<GridPane layoutY="14.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
<columnConstraints>
<ColumnConstraints hgrow="SOMETIMES" maxWidth="170.0" minWidth="10.0" prefWidth="170.0" />
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="100.0" />
<ColumnConstraints hgrow="SOMETIMES" maxWidth="170.0" minWidth="10.0" prefWidth="170.0" />
<ColumnConstraints hgrow="NEVER" prefWidth="170.0" />
<ColumnConstraints hgrow="NEVER" prefWidth="100.0" />
<ColumnConstraints hgrow="NEVER" prefWidth="170.0" />
</columnConstraints>
<rowConstraints>
<RowConstraints maxHeight="60.0" minHeight="60.0" prefHeight="30.0" vgrow="SOMETIMES" />
<RowConstraints minHeight="10.0" prefHeight="435.0" vgrow="SOMETIMES" />
<RowConstraints maxHeight="80.0" minHeight="80.0" prefHeight="80.0" vgrow="SOMETIMES" />
<RowConstraints prefHeight="50.0" vgrow="NEVER" />
<RowConstraints prefHeight="419.5" vgrow="NEVER" />
<RowConstraints prefHeight="50.0" vgrow="NEVER" />
<RowConstraints prefHeight="80.0" vgrow="NEVER" />
</rowConstraints>
<children>
<Button fx:id="hostGameBtn" mnemonicParsing="false" onAction="#hostGamePressed" text="Start Game" GridPane.columnIndex="1" GridPane.halignment="CENTER" GridPane.rowIndex="2" GridPane.valignment="CENTER">
<Button fx:id="hostGameBtn" mnemonicParsing="false" onAction="#hostGamePressed" text="Start Game" GridPane.columnIndex="1" GridPane.halignment="CENTER" GridPane.rowIndex="3" GridPane.valignment="CENTER">
<font>
<Font size="20.0" />
</font>
@ -50,6 +52,16 @@
<ImageView fx:id="mapImage" pickOnBounds="true" preserveRatio="true" GridPane.columnIndex="1" GridPane.halignment="CENTER" GridPane.hgrow="ALWAYS" GridPane.rowIndex="1" GridPane.valignment="CENTER" GridPane.vgrow="ALWAYS" />
<Button fx:id="previousButton" maxHeight="80.0" maxWidth="80.0" mnemonicParsing="false" onAction="#previousImage" GridPane.halignment="CENTER" GridPane.rowIndex="1" GridPane.valignment="CENTER" />
<Button fx:id="nextButton" maxHeight="80.0" maxWidth="80.0" mnemonicParsing="false" onAction="#nextImage" GridPane.columnIndex="2" GridPane.halignment="CENTER" GridPane.rowIndex="1" GridPane.valignment="CENTER" />
<Slider fx:id="sliderLength" prefHeight="14.0" prefWidth="100.0" GridPane.columnIndex="1" GridPane.halignment="CENTER" GridPane.rowIndex="2">
<GridPane.margin>
<Insets left="50.0" right="50.0" />
</GridPane.margin>
</Slider>
<Label fx:id="lblLength" GridPane.columnIndex="1" GridPane.halignment="CENTER" GridPane.rowIndex="2" GridPane.valignment="BOTTOM">
<GridPane.margin>
<Insets />
</GridPane.margin>
</Label>
</children>
</GridPane>
</children>

@ -1,5 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.image.*?>
<?import java.lang.*?>
<?import javafx.geometry.*?>
<?import javafx.scene.chart.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<?import javafx.scene.text.*?>
<?import java.lang.*?>
<?import javafx.geometry.*?>
<?import javafx.scene.chart.*?>
@ -26,6 +33,7 @@
<?import javafx.scene.layout.Pane?>
<?import javafx.scene.layout.RowConstraints?>
<?import javafx.scene.layout.StackPane?>
<?import javafx.scene.layout.VBox?>
<?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" xmlns:fx="http://javafx.com/fxml/1" fx:controller="visualiser.Controllers.RaceViewController">
@ -41,6 +49,13 @@
<rowConstraints>
<RowConstraints minHeight="10.0" vgrow="SOMETIMES" />
</rowConstraints>
<children>
<StackPane fx:id="nextMarkPane" alignment="TOP_CENTER" mouseTransparent="true" prefHeight="150.0" prefWidth="150.0" snapToPixel="false">
<children>
<fx:include fx:id="nextMark" source="nextMark.fxml" />
</children>
</StackPane>
</children>
</GridPane>
<Pane prefHeight="200.0" prefWidth="400.0" visible="false">
<children>
@ -85,23 +100,39 @@
</Accordion>
</children>
</Pane>
<Label fx:id="timer" alignment="CENTER" maxHeight="20.0" mouseTransparent="true" text="0:0">
<font>
<Font name="System Bold" size="15.0" />
</font>
</Label>
<Label fx:id="FPS" mouseTransparent="true" text="FPS: 0" AnchorPane.leftAnchor="0.0" AnchorPane.topAnchor="0.0">
<font>
<Font name="System Bold" size="15.0" />
</font>
<GridPane fx:id="playerHealthContainer" prefHeight="75.0" prefWidth="75.0" AnchorPane.leftAnchor="0.0" AnchorPane.topAnchor="0.0">
<columnConstraints>
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="100.0" />
</columnConstraints>
<rowConstraints>
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
</rowConstraints>
<padding>
<Insets top="20.0" />
<Insets left="10.0" top="10.0" />
</padding>
</Label>
<Label fx:id="timeZone" mouseTransparent="true" text="Label" AnchorPane.leftAnchor="0.0" AnchorPane.topAnchor="0.0">
</GridPane>
<VBox alignment="CENTER" prefHeight="54.0" prefWidth="608.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
<children>
<Label fx:id="timer" alignment="CENTER" maxHeight="20.0" text="0:0">
<font>
<Font name="System Bold" size="15.0" />
</font>
</Label>
<Label fx:id="timeZone" text="Label">
<font>
<Font name="System Bold" size="15.0" />
</font>
</Label>
</children>
</VBox>
<Label fx:id="FPS" text="FPS: 0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0">
<font>
<Font name="System Bold" size="15.0" />
</font>
<padding>
<Insets bottom="200.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>
@ -172,6 +203,30 @@
</AnchorPane>
</children>
</AnchorPane>
<ImageView fx:id="imageView" fitHeight="150.0" fitWidth="200.0" pickOnBounds="true" preserveRatio="true" />
<StackPane fx:id="deathPane" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
<children>
<AnchorPane mouseTransparent="true" opacity="0.31" prefHeight="200.0" prefWidth="200.0" style="-fx-background-color: grey;" />
<Label alignment="CENTER" text="You have died..." StackPane.alignment="TOP_CENTER">
<font>
<Font size="34.0" />
</font>
<StackPane.margin>
<Insets top="100.0" />
</StackPane.margin>
</Label>
<Label alignment="CENTER" text="But you can still watch the race!" StackPane.alignment="TOP_CENTER">
<StackPane.margin>
<Insets top="165.0" />
</StackPane.margin>
</Label>
<Button alignment="CENTER" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" mnemonicParsing="false" onAction="#deathOKPressed" prefHeight="25.0" prefWidth="52.0" text="OK" StackPane.alignment="TOP_CENTER">
<StackPane.margin>
<Insets top="200.0" />
</StackPane.margin>
</Button>
</children>
</StackPane>
</children>
</AnchorPane>
<AnchorPane fx:id="infoWrapper" focusTraversable="true" minHeight="0.0" minWidth="0.0" prefHeight="160.0" prefWidth="200.0" visible="false">
@ -189,10 +244,10 @@
<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" />
<TableColumn fx:id="boatTeamColumn" prefWidth="135.0" text="Team" />
<TableColumn fx:id="boatMarkColumn" prefWidth="142.0" text="Mark" />
<TableColumn fx:id="boatHealthColumn" prefWidth="110.0" text="Health" />
<TableColumn fx:id="boatSpeedColumn" prefWidth="87.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">

@ -0,0 +1,38 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.image.Image?>
<?import javafx.scene.image.ImageView?>
<?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?>
<GridPane fx:id="arrowGridPane" alignment="TOP_CENTER" maxHeight="-Infinity" maxWidth="-Infinity" xmlns="http://javafx.com/javafx/8.0.112" xmlns:fx="http://javafx.com/fxml/1" fx:controller="visualiser.Controllers.NextMarkController">
<children>
<Pane fx:id="pane2d" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="112.0" prefWidth="112.0">
<children>
<StackPane fx:id="arrowStackPane2d" prefHeight="112.0" prefWidth="112.0">
<children>
<ImageView fx:id="arrowImage" fitHeight="75.0" fitWidth="25.0">
<image>
<Image url="@../../images/nextMarkArrow2d.png" />
</image>
</ImageView>
</children>
</StackPane>
</children>
</Pane>
<Pane fx:id="pane3d" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="112.0" prefWidth="112.0">
<children>
<StackPane fx:id="arrowStackPane3d" prefHeight="112.0" prefWidth="112.0" />
</children>
</Pane>
</children>
<columnConstraints>
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" />
</columnConstraints>
<rowConstraints>
<RowConstraints minHeight="10.0" vgrow="SOMETIMES" />
</rowConstraints>
</GridPane>

@ -0,0 +1,33 @@
package network.MessageDecoders;
import network.Exceptions.InvalidMessageException;
import network.MessageEncoders.RaceVisionByteEncoder;
import network.Messages.BoatState;
import org.junit.Before;
import org.junit.Test;
import static org.testng.Assert.*;
/**
* Test for the {@link network.Messages.BoatState} encoder and decoder.
*/
public class BoatStateDecoderTest {
private BoatState originalMessage;
private BoatState decodedMessage;
@Before
public void setUp() throws InvalidMessageException {
originalMessage = new BoatState(0, 100);
byte[] encodedMessage = RaceVisionByteEncoder.encode(originalMessage);
BoatStateDecoder decoder = new BoatStateDecoder();
decoder.decode(encodedMessage);
decodedMessage = decoder.getMessage();
}
@Test
public void decodingEqualsOriginal() {
assertEquals(originalMessage.getSourceID(), decodedMessage.getSourceID());
assertEquals(originalMessage.getBoatHealth(), decodedMessage.getBoatHealth());
}
}
Loading…
Cancel
Save