Merge branch 'storyD-3D' into 'master'
D and A D) As user I wish to play on a modern day 3D view Description: The current base of the game is run on a 2D dot and line canvas, this will be overhauled and replaced with a 3D view of the game. Acceptance Criteria: - Boats, and markers in the race are now 3D. - Sea has a surface. - Race runs and is reflected on the 3D view. - Skybox must be present. - Boundary must be shown (2D or 3D) - Annotations do not need to be shown. Tests: - When the FXML's are split make sure that the race still changes panes to reflect each stage of the race, prestart, racing, and finish. - The boats headings on the 3D models are the correct orientation (if they aren't facting forward on load please tell Fan-Wu as this should be an issue with the stl file). - The camera is "fixed" for now. - The base pane of the race is a stack pane so we can stack an annotations layer, or control layer, etc in the future. A) [V] As a player I would like for the style of the application to be informative Acceptance criteria: * The direction to the next mark is shown in 3rd person view * The next mark to be rounded is highlighted * Player is notified with a sound effect when the mark is rounded * Collisions are made clear and give visual and audio feedback * The score table overlays on the race and does not affect scaling See merge request !46
@@ -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");
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|
||||||
@@ -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,9 +194,14 @@ 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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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());
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -138,11 +138,13 @@ 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);
|
Subject3D subject = new Subject3D(mesh,0);
|
||||||
subjects.add(subject);
|
subjects.add(subject);
|
||||||
|
|
||||||
playerBoatToSet.setDistance(50);
|
playerBoatToSet.setDistance(50);
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -3,29 +3,37 @@ package visualiser.Controllers;
|
|||||||
import com.interactivemesh.jfx.importer.stl.StlMeshImporter;
|
import com.interactivemesh.jfx.importer.stl.StlMeshImporter;
|
||||||
import javafx.animation.AnimationTimer;
|
import javafx.animation.AnimationTimer;
|
||||||
import javafx.application.Platform;
|
import javafx.application.Platform;
|
||||||
|
import javafx.beans.value.ChangeListener;
|
||||||
import javafx.collections.FXCollections;
|
import javafx.collections.FXCollections;
|
||||||
import javafx.collections.ListChangeListener;
|
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.effect.Light;
|
||||||
import javafx.scene.input.KeyCode;
|
import javafx.scene.input.KeyCode;
|
||||||
import javafx.scene.input.KeyEvent;
|
import javafx.scene.input.KeyEvent;
|
||||||
import javafx.scene.layout.GridPane;
|
import javafx.scene.layout.GridPane;
|
||||||
import javafx.scene.layout.StackPane;
|
import javafx.scene.layout.StackPane;
|
||||||
import javafx.scene.shape.MeshView;
|
import javafx.scene.paint.Color;
|
||||||
import javafx.scene.shape.Sphere;
|
import javafx.scene.paint.Material;
|
||||||
|
import javafx.scene.paint.PhongMaterial;
|
||||||
|
import javafx.scene.shape.*;
|
||||||
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.layout.*;
|
||||||
|
import visualiser.model.*;
|
||||||
import visualiser.gameController.Keys.KeyFactory;
|
import visualiser.gameController.Keys.KeyFactory;
|
||||||
import visualiser.layout.Subject3D;
|
import visualiser.layout.Subject3D;
|
||||||
import visualiser.layout.View3D;
|
import visualiser.layout.View3D;
|
||||||
@@ -61,6 +69,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;
|
||||||
@@ -121,9 +138,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 +205,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 +229,122 @@ 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);
|
||||||
// Position and add each mark to view
|
viewSubjects.addAll(skyBox.getSkyBoxPlanes());
|
||||||
for (Mark mark : raceState.getMarks()) {
|
|
||||||
Subject3D subject = new Subject3D(new Sphere(2));
|
|
||||||
subject.setX(gpsConverter.convertGPS(mark.getPosition()).getX());
|
|
||||||
subject.setZ(gpsConverter.convertGPS(mark.getPosition()).getY());
|
|
||||||
|
|
||||||
viewSubjects.add(subject);
|
// 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
|
||||||
|
for(Mark mark: race.getVisualiserRaceState().getMarks()) {
|
||||||
|
MeshView mesh = new MeshView(importerMark.getImport());
|
||||||
|
Subject3D markModel = new Subject3D(mesh, mark.getSourceID());
|
||||||
|
|
||||||
|
markModel.setX(gpsConverter.convertGPS(mark.getPosition()).getX());
|
||||||
|
markModel.setZ(gpsConverter.convertGPS(mark.getPosition()).getY());
|
||||||
|
|
||||||
|
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());
|
||||||
|
}
|
||||||
|
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()).setMaterial(markColor);
|
||||||
|
if(nextMark.getMark2() != null) {
|
||||||
|
view3D.getShape(nextMark.getMark2().getSourceID()).setMaterial(markColor);
|
||||||
|
}
|
||||||
|
Subject3D shockwave = new Shockwave(10);
|
||||||
|
|
||||||
|
viewSubjects.add(shockwave);
|
||||||
|
|
||||||
|
boat.legProperty().addListener((o, prev, curr) -> swapColours(curr));
|
||||||
|
boat.hasCollidedProperty().addListener((o, prev, curr) -> showCollision(boatModel, 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(prev);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// Bind zooming to scrolling
|
// Bind zooming to scrolling
|
||||||
view3D.setOnScroll(e -> {
|
view3D.setOnScroll(e -> {
|
||||||
view3D.updateDistance(e.getDeltaY());
|
view3D.updateDistance(e.getDeltaY());
|
||||||
@@ -294,6 +390,112 @@ public class RaceViewController extends Controller {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void showCollision(Subject3D boat, Subject3D shockwave) {
|
||||||
|
AnimationTimer shockFront = new AnimationTimer() {
|
||||||
|
double opacity = 1;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handle(long now) {
|
||||||
|
shockwave.setX(boat.getPosition().getX());
|
||||||
|
shockwave.setY(boat.getPosition().getY());
|
||||||
|
shockwave.setZ(boat.getPosition().getZ());
|
||||||
|
|
||||||
|
if(opacity <= 0) {
|
||||||
|
shockwave.getMesh().setMaterial(new PhongMaterial(new Color(1,0,0,0)));
|
||||||
|
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();
|
||||||
|
/*
|
||||||
|
try {
|
||||||
|
VisualiserBoat boat = visualiserRace.getVisualiserRaceState().getBoat(subject3D.getSourceID());
|
||||||
|
this.pointToMark = 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() + 10);
|
||||||
|
nextMarkArrow.setHeading(headingToMark.degrees());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
pointToMark.start();
|
||||||
|
} catch (BoatNotFoundException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}*/
|
||||||
|
}
|
||||||
|
|
||||||
|
private void removeThirdPersonAnnotations(Subject3D subject3D) {
|
||||||
|
viewSubjects.remove(nextMarkArrow);
|
||||||
|
arrowToNextMark.stop();
|
||||||
|
/*
|
||||||
|
try {
|
||||||
|
VisualiserBoat boat = visualiserRace.getVisualiserRaceState().getBoat(subject3D.getSourceID());
|
||||||
|
boat.positionProperty().removeListener(pointToMark);
|
||||||
|
} catch (BoatNotFoundException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}*/
|
||||||
|
//pointToMark.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();
|
||||||
|
|
||||||
|
Shape3D start1 = view3D.getShape(start.getMark1().getSourceID());
|
||||||
|
Shape3D end1 = view3D.getShape(end.getMark1().getSourceID());
|
||||||
|
|
||||||
|
Material nextMark = start1.getMaterial();
|
||||||
|
Material lastMark = end1.getMaterial();
|
||||||
|
|
||||||
|
start1.setMaterial(lastMark);
|
||||||
|
if(start.getMark2() != null) {
|
||||||
|
Shape3D start2 = view3D.getShape(start.getMark2().getSourceID());
|
||||||
|
start2.setMaterial(lastMark);
|
||||||
|
}
|
||||||
|
|
||||||
|
end1.setMaterial(nextMark);
|
||||||
|
if(end.getMark2() != null) {
|
||||||
|
Shape3D end2 = view3D.getShape(end.getMark2().getSourceID());
|
||||||
|
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.
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
|
System.out.println(points.size());
|
||||||
|
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;
|
||||||
@@ -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));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1,65 +1,64 @@
|
|||||||
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
|
||||||
*/
|
*/
|
||||||
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,6 +93,7 @@ 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 value value to normalise
|
||||||
* @param min current min
|
* @param min current min
|
||||||
* @param max current max
|
* @param max current max
|
||||||
@@ -109,6 +109,7 @@ 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
|
||||||
@@ -126,11 +127,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,29 @@ 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
|
||||||
*/
|
*/
|
||||||
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 +59,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 Shape3D from source ID
|
||||||
|
*/
|
||||||
|
private Map<Integer, Shape3D> 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);
|
private AnimationTimer trackBoat;
|
||||||
/**
|
|
||||||
* 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);
|
|
||||||
/**
|
|
||||||
* Single listener for subject position (z) changes
|
|
||||||
*/
|
|
||||||
private ChangeListener<? super Number> pivotZ = (o, prev, curr) -> pivot.setZ((double)curr);
|
|
||||||
/**
|
/**
|
||||||
* Distance to switch from third person to bird's eye
|
* Distance to switch from third person to bird's eye
|
||||||
*/
|
*/
|
||||||
private double THIRD_PERSON_LIMIT = 100;
|
private final double THIRD_PERSON_LIMIT = 100;
|
||||||
|
/**
|
||||||
|
* Distance to stop zoom
|
||||||
|
*/
|
||||||
|
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());
|
||||||
|
if (fill) {
|
||||||
scene.setFill(new Color(0.2, 0.6, 1, 1));
|
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.getMesh());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Shape3D 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());
|
this.trackBoat = new AnimationTimer() {
|
||||||
setYaw(target.getHeading().getAngle());
|
@Override
|
||||||
|
public void handle(long now) {
|
||||||
target.getPosition().xProperty().addListener(pivotX);
|
updatePivot(target.get().getPosition());
|
||||||
target.getPosition().yProperty().addListener(pivotY);
|
setYaw(target.get().getHeading().getAngle());
|
||||||
target.getPosition().zProperty().addListener(pivotZ);
|
}
|
||||||
target.getHeading().angleProperty().addListener(pivotHeading);
|
};
|
||||||
|
trackBoat.start();
|
||||||
this.setDistance(THIRD_PERSON_LIMIT);
|
|
||||||
this.setPitch(20);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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());
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
|
After Width: | Height: | Size: 5.0 MiB |
|
After Width: | Height: | Size: 3.6 MiB |
|
After Width: | Height: | Size: 4.8 MiB |
|
After Width: | Height: | Size: 4.4 MiB |
|
After Width: | Height: | Size: 4.6 MiB |
|
After Width: | Height: | Size: 2.4 MiB |
|
After Width: | Height: | Size: 78 KiB |
|
After Width: | Height: | Size: 103 KiB |
|
After Width: | Height: | Size: 85 KiB |
|
After Width: | Height: | Size: 99 KiB |
|
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>
|
||||||