Merge branch 'master' into new_3d_assets
# Conflicts: # racevisionGame/src/main/java/visualiser/Controllers/RaceViewController.java
This commit is contained in:
+30
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -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();
|
||||
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
|
||||
// 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());
|
||||
|
||||
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.
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user