Merge branch 'master' into storyD-3D

# Conflicts:
#	racevisionGame/src/main/java/visualiser/Controllers/RaceController.java
#	racevisionGame/src/main/java/visualiser/layout/Subject3D.java
main
cbt24 8 years ago
commit 2508b847b8

@ -82,7 +82,7 @@ public class HostController extends Controller {
AnimationTimer rotate = new AnimationTimer() {
@Override
public void handle(long now) {
subject.setHeading(subject.getHeading() + 0.1);
subject.setHeading(subject.getHeading().getAngle() + 0.1);
}
};
rotate.start();

@ -13,7 +13,6 @@ import javafx.scene.control.*;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.Pane;
import javafx.scene.layout.StackPane;
import javafx.scene.shape.MeshView;
import javafx.scene.shape.Sphere;
@ -21,13 +20,12 @@ import javafx.scene.transform.Translate;
import javafx.util.Callback;
import network.Messages.Enums.RaceStatusEnum;
import shared.dataInput.RaceDataSource;
import shared.exceptions.BoatNotFoundException;
import shared.exceptions.MarkNotFoundException;
import shared.model.Leg;
import shared.model.Mark;
import visualiser.app.App;
import visualiser.gameController.ControllerClient;
import visualiser.gameController.Keys.ControlKey;
import visualiser.gameController.Keys.KeyFactory;
import visualiser.layout.MarkRadius;
import visualiser.layout.Subject3D;
import visualiser.layout.View3D;
@ -36,7 +34,6 @@ import visualiser.utils.GPSConverter;
import java.io.IOException;
import java.net.URL;
import java.util.List;
import java.util.Optional;
import java.util.ResourceBundle;
import java.util.logging.Level;
@ -76,7 +73,7 @@ public class RaceController extends Controller {
@FXML private GridPane canvasBase;
@FXML private SplitPane race;
@FXML private SplitPane racePane;
/**
* This is the pane we place the actual arrow control inside of.
@ -104,7 +101,7 @@ public class RaceController extends Controller {
infoTableShow = true;
// Initialise keyboard handler
race.addEventFilter(KeyEvent.KEY_PRESSED, event -> {
racePane.addEventFilter(KeyEvent.KEY_PRESSED, event -> {
String codeString = event.getCode().toString();
if (codeString.equals("TAB")){toggleTable();}
@ -130,7 +127,7 @@ public class RaceController extends Controller {
Optional<ButtonType> result = alert.showAndWait();
if (result.get() == ButtonType.OK) {
parent.endEvent();
race.setVisible(false);
racePane.setVisible(false);
App.app.showMainStage(App.getStage());
}
} else {
@ -139,7 +136,7 @@ public class RaceController extends Controller {
alert.setContentText("Do you wish to quit the race?");
Optional<ButtonType> result = alert.showAndWait();
if (result.get() == ButtonType.OK) {
race.setVisible(false);
racePane.setVisible(false);
App.app.showMainStage(App.getStage());
}
}
@ -179,13 +176,14 @@ public class RaceController extends Controller {
}
private void initialiseView3D(VisualiserRaceEvent race) {
ObservableList<Subject3D> subjects = FXCollections.observableArrayList();
viewSubjects = FXCollections.observableArrayList();
//read 3d Assets
// Import boat mesh
URL asset = HostController.class.getClassLoader().getResource("assets/V1.2 Complete Boat.stl");
StlMeshImporter importer = new StlMeshImporter();
importer.read(asset);
// Configure camera angles and control
URL markerAsset = HostController.class.getClassLoader().getResource("assets/Bouy V1.1.stl");
StlMeshImporter importerMark = new StlMeshImporter();
importerMark.read(markerAsset);
@ -198,22 +196,19 @@ public class RaceController extends Controller {
view3D.setDistance(1050);
view3D.setYaw(0);
view3D.setPitch(60);
//view3D.rotateCamera(-90, 1, 0, 0);
//view3D.updatePosition(0, 200, 0);
RaceDataSource raceData = visualiserRace.getVisualiserRaceState().getRaceDataSource();
double lat1 = raceData.getMapTopLeft().getLatitude();
double long1 = raceData.getMapTopLeft().getLongitude();
double lat2 = raceData.getMapBottomRight().getLatitude();
double long2 = raceData.getMapBottomRight().getLongitude();
System.out.println(view3D.getWidth());
System.out.println(view3D.getHeight());
final GPSConverter gpsConverter = new GPSConverter(lat1, long1, lat2, long2, (int)450, (int)450);
view3D.enableTracking();
canvasBase.add(view3D, 0, 0);
view3D.setItems(subjects);
canvasBase.getChildren().add(0, view3D);
// Set up projection from GPS to view
RaceDataSource raceData = visualiserRace.getVisualiserRaceState().getRaceDataSource();
final GPSConverter gpsConverter = new GPSConverter(raceData, 450, 450);
view3D.setItems(viewSubjects);
// Position and add each mark to view
for(Mark mark: race.getVisualiserRaceState().getMarks()) {
Subject3D subject = new Subject3D(new Sphere(2));
subject.setX(gpsConverter.convertGPS(mark.getPosition()).getX());
subject.setZ(gpsConverter.convertGPS(mark.getPosition()).getY());
MeshView mesh = new MeshView(importerMark.getImport());
Subject3D markModel = new Subject3D(mesh);
Subject3D markRadius = new MarkRadius(3);
@ -224,34 +219,53 @@ public class RaceController extends Controller {
markRadius.setX(x);
markRadius.setZ(z);
subjects.add(markModel);
subjects.add(markRadius);
viewSubjects.add(markModel);
viewSubjects.add(markRadius);
}
// Position and add each boat to view
for(VisualiserBoat boat: race.getVisualiserRaceState().getBoats()) {
MeshView mesh = null;
MeshView mesh;
if(boat.getSourceID() == race.getVisualiserRaceState().getPlayerBoatID()) {
mesh = new MeshView(importer.getImport());
} else {
mesh = new MeshView(importerBurgerBoat.getImport());
}
Subject3D subject = new Subject3D(mesh);
subjects.add(subject);
viewSubjects.add(subject);
// Track this boat's movement with the new subject
AnimationTimer trackBoat = new AnimationTimer() {
@Override
public void handle(long now) {
subject.setHeading(boat.getBearing().degrees());
subject.setX(gpsConverter.convertGPS(boat.getPosition()).getX());
subject.setZ(gpsConverter.convertGPS(boat.getPosition()).getY());
if(boat.getSourceID() == race.getVisualiserRaceState().getPlayerBoatID()) {
//view3D.updatePivot(subject.getPosition());
}
//view3D.setYaw(boat.getBearing().degrees());
}
};
trackBoat.start();
}
// Fix initial bird's-eye position
view3D.updatePivot(new Translate(250, 0, 210));
// Bind zooming to scrolling
view3D.setOnScroll(e -> {
view3D.updateDistance(e.getDeltaY());
});
// Bind zooming to keypress (Z/X default)
racePane.addEventFilter(KeyEvent.KEY_PRESSED, e -> {
ControlKey key = keyFactory.getKey(e.getCode().toString());
if(key != null) {
switch (key.toString()) {
case "Zoom In":
view3D.updateDistance(-10);
break;
case "Zoom Out":
view3D.updateDistance(10);
break;
}
}
});
}
@ -434,7 +448,7 @@ public class RaceController extends Controller {
initialiseRace();
//Display this controller.
race.setVisible(true);
racePane.setVisible(true);
}
/**
@ -442,7 +456,7 @@ public class RaceController extends Controller {
* @param boats boats there are in the race.
*/
public void finishRace(ObservableList<VisualiserBoat> boats) {
race.setVisible(false);
racePane.setVisible(false);
parent.enterFinish(boats);
}
@ -483,7 +497,7 @@ public class RaceController extends Controller {
//Return to main screen if we lose connection.
if (!visualiserRace.getServerConnection().isAlive()) {
race.setVisible(false);
racePane.setVisible(false);
//parent.enterTitle();
try {
App.app.showMainStage(App.getStage());
@ -505,10 +519,10 @@ public class RaceController extends Controller {
* toggles if the info table is shown
*/
private void toggleTable() {
double tablePercent = 1 - (boatPlacingColumn.getPrefWidth() + boatTeamColumn.getPrefWidth() + boatMarkColumn.getPrefWidth() + boatSpeedColumn.getPrefWidth())/race.getWidth();
double tablePercent = 1 - (boatPlacingColumn.getPrefWidth() + boatTeamColumn.getPrefWidth() + boatMarkColumn.getPrefWidth() + boatSpeedColumn.getPrefWidth())/racePane.getWidth();
if (infoTableShow){
race.setDividerPositions(tablePercent);
racePane.setDividerPositions(tablePercent);
arrowPane.setScaleX(0.5);
arrowPane.setScaleY(0.5);
@ -516,7 +530,7 @@ public class RaceController extends Controller {
arrowPane.setTranslateY(0 - arrowPane.getScene().getHeight()/4);
}else{
race.setDividerPositions(1);
racePane.setDividerPositions(1);
arrowPane.setScaleX(1);
arrowPane.setScaleY(1);

@ -1,6 +1,5 @@
package visualiser.gameController.Keys;
import javafx.scene.input.KeyCode;
import network.Messages.Enums.BoatActionEnum;
/**
@ -45,7 +44,7 @@ public abstract class ControlKey {
/**
* What this key should do when the command is issued for it to do its job.
*/
public abstract void onAction();//may want to make it take in a visualiser and stuff in the future.
public abstract void onAction();
/**
* What to do when the key is held

@ -33,9 +33,6 @@ public class Subject3D {
this.heading = new Rotate(0, Rotate.Y_AXIS);
this.mesh.getTransforms().addAll(position, heading, new Rotate(90, Rotate.X_AXIS), new Rotate(180, Rotate.Y_AXIS));
// this.position.xProperty().addListener(((observable, oldValue, newValue) -> System.out.println("Boat x: " + newValue)));
// this.position.yProperty().addListener(((observable, oldValue, newValue) -> System.out.println("Boat y: " + newValue)));
// this.position.zProperty().addListener(((observable, oldValue, newValue) -> System.out.println("Boat z: " + newValue)));
}
public Shape3D getMesh() {
@ -46,6 +43,10 @@ public class Subject3D {
return this.position;
}
public Rotate getHeading() {
return heading;
}
public void setX(double x) {
position.setX(x);
}
@ -58,10 +59,6 @@ public class Subject3D {
position.setZ(z);
}
public double getHeading() {
return heading.getAngle();
}
public void setHeading(double angle) {
heading.setAngle(angle);
}

@ -1,27 +1,43 @@
package visualiser.layout;
import javafx.beans.value.ChangeListener;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
import javafx.geometry.Point3D;
import javafx.scene.Group;
import javafx.scene.PerspectiveCamera;
import javafx.scene.SubScene;
import javafx.scene.input.PickResult;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Shape3D;
import javafx.scene.transform.Rotate;
import javafx.scene.transform.Translate;
import java.util.HashMap;
import java.util.Map;
/**
* Control for rendering 3D objects visible through a PerspectiveCamera. Implements Adapter Pattern to
* interface with camera, and allows clients to add shapes to the scene. All scenes contain sea plane and
* sky box, whose textures are set with special methods.
*/
public class View3D extends Pane {
/**
* Container for group and camera
*/
private SubScene scene;
/**
* Observable list of renderable items
*/
private ObservableList<Subject3D> items;
/**
* Map for selecting Subject3D from Shape3D
*/
private Map<Shape3D, Subject3D> selectionMap;
/**
* Subject tracked by camera
*/
private Subject3D target;
/**
* Rendering container for shapes
*/
@ -50,16 +66,36 @@ public class View3D extends Pane {
* Angle between ground plane and camera direction
*/
private Rotate pitch;
private PerspectiveCamera camera;
/**
* Single listener for subject heading changes
*/
private ChangeListener<? super Number> pivotHeading = (o, prev, curr) -> yaw.setAngle((double)curr);
/**
* Single listener for subject position (x) changes
*/
private ChangeListener<? super Number> pivotX = (o, prev, curr) -> pivot.setX((double)curr);
/**
* Single listener for subject position (y) changes
*/
private ChangeListener<? super Number> pivotY = (o, prev, curr) -> pivot.setY((double)curr);
/**
* 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
*/
private double THIRD_PERSON_LIMIT = 100;
/**
* Default constructor for View3D. Sets up Scene and PerspectiveCamera.
*/
public View3D() {
world = new Group();
this.world = new Group();
this.selectionMap = new HashMap<>();
this.target = null;
this.scene = new SubScene(world, 300, 300);
SubScene scene = new SubScene(world, 300, 300);
scene.widthProperty().bind(this.widthProperty());
scene.heightProperty().bind(this.heightProperty());
scene.setFill(new Color(0.2, 0.6, 1, 1));
@ -88,24 +124,77 @@ public class View3D extends Pane {
yaw = new Rotate(0, Rotate.Y_AXIS);
pitch = new Rotate(0, Rotate.X_AXIS);
camera.getTransforms().addAll(pivot, yaw, pitch, distance);
centerCamera();
this.camera = camera;
return camera;
}
/**
* Provide the list of subjects to be automatically added or removed from the view as the list
* changes.
* @param items list managed by client
*/
public void setItems(ObservableList<Subject3D> items) {
this.items = items;
this.items.addListener((ListChangeListener<? super Subject3D>) c -> {
while(c.next()) {
if (c.wasRemoved() || c.wasAdded()) {
for (Subject3D shape : c.getRemoved()) world.getChildren().remove(shape.getMesh());
for (Subject3D shape : c.getAddedSubList()) world.getChildren().add(shape.getMesh());
for (Subject3D shape : c.getRemoved()) {
world.getChildren().remove(shape.getMesh());
selectionMap.remove(shape.getMesh());
}
for (Subject3D shape : c.getAddedSubList()) {
world.getChildren().add(shape.getMesh());
selectionMap.put(shape.getMesh(), shape);
}
}
}
});
}
/**
* Intercept mouse clicks on subjects in view. The applied listener cannot be removed.
*/
public void enableTracking() {
scene.setOnMousePressed(e -> {
PickResult result = e.getPickResult();
if(result != null && result.getIntersectedNode() != null && result.getIntersectedNode() instanceof Shape3D) {
trackSubject(selectionMap.get(result.getIntersectedNode()));
}
});
}
/**
* Stop camera from following the last selected subject
*/
private void untrackSubject() {
if(target != null) {
target.getPosition().xProperty().removeListener(pivotX);
target.getPosition().yProperty().removeListener(pivotY);
target.getPosition().zProperty().removeListener(pivotZ);
target.getHeading().angleProperty().removeListener(pivotHeading);
}
}
/**
* Set camera to follow the selected subject
* @param subject to track
*/
private void trackSubject(Subject3D subject) {
untrackSubject();
target = subject;
updatePivot(target.getPosition());
setYaw(target.getHeading().getAngle());
target.getPosition().xProperty().addListener(pivotX);
target.getPosition().yProperty().addListener(pivotY);
target.getPosition().zProperty().addListener(pivotZ);
target.getHeading().angleProperty().addListener(pivotHeading);
this.setDistance(THIRD_PERSON_LIMIT);
this.setPitch(20);
}
public void setNearClip(double nearClip) {
this.nearClip = nearClip;
}
@ -114,18 +203,16 @@ public class View3D extends Pane {
this.farClip = farClip;
}
/**
* Sets the coordinates of the camera pivot once.
* @param pivot source of coordinates
*/
public void updatePivot(Translate pivot) {
this.pivot.setX(pivot.getX());
this.pivot.setY(pivot.getY());
this.pivot.setZ(pivot.getZ());
}
public void updatePosition(double x, double y, double z) {
this.distance.setX(x);
this.distance.setY(y);
this.distance.setZ(z);
}
/**
* Set distance of camera from pivot
* @param distance in units
@ -134,6 +221,27 @@ public class View3D extends Pane {
this.distance.setZ(-distance);
}
/**
* Adds delta to current distance and changes camera mode if applicable.
* Third person limit specifies the distance at which a third person camera
* switches to bird's-eye, remaining focused on the same position.
* @param delta amount to change distance by
*/
public void updateDistance(double delta) {
double distance = -this.distance.getZ() + delta;
if(distance <= 0) {
this.setDistance(0);
} else if(distance > THIRD_PERSON_LIMIT) {
untrackSubject();
this.setYaw(0);
this.setPitch(60);
this.setDistance(distance);
} else {
this.setDistance(distance);
}
}
/**
* Set angle of camera from z-axis along ground
* @param yaw in degrees
@ -149,13 +257,4 @@ public class View3D extends Pane {
public void setPitch(double pitch) {
this.pitch.setAngle(-pitch);
}
public void centerCamera(){
}
public void rotateCamera(double angle, double x, double y, double z){
camera.setRotationAxis(new Point3D(x, y, z));
camera.setRotate(-90);
}
}

@ -1,26 +1,41 @@
package visualiser.utils;
import shared.dataInput.RaceDataSource;
import shared.model.GPSCoordinate;
import visualiser.model.GraphCoordinate;
/**
* Created by fwy13 on 7/09/17.
* Converts GPS coordinates to view volume coordinates. Longitudes are equally spaced at all latitudes,
* which leads to inaccurate distance measurements close to the poles. This is acceptable as races are
* not likely to be set there.
*/
public class GPSConverter {
double longRight;
double longLeft;
double latBottom;
double latTop;
int paneWidth;
int paneHeight;
public GPSConverter(double latTop, double longLeft, double latBottom, double longRight, double paneWidth, double paneHeight){
this.longRight = longRight;
this.longLeft = longLeft;
this.latBottom = latBottom;
this.latTop = latTop;
this.paneWidth = (int)paneWidth;
this.paneHeight = (int)paneHeight;
private double longRight;
private double longLeft;
private double latBottom;
private double latTop;
/**
* Conversion factor from longitude to view units
*/
private double longitudeFactor;
/**
* Conversion factor from latitude to view units
*/
private double latitudeFactor;
/**
* Set up projection with default view boundaries from RaceDataSource
* @param source for view boundaries
* @param longitudeFactor separation of a degree of longitude in view units
* @param latitudeFactor separation of a degree of latitude in view units
*/
public GPSConverter(RaceDataSource source, double longitudeFactor, double latitudeFactor) {
this.latTop = source.getMapTopLeft().getLatitude();
this.longLeft = source.getMapTopLeft().getLongitude();
this.latBottom = source.getMapBottomRight().getLatitude();
this.longRight = source.getMapBottomRight().getLongitude();
this.longitudeFactor = longitudeFactor;
this.latitudeFactor = latitudeFactor;
}
/**
@ -46,11 +61,9 @@ public class GPSConverter {
double longProportion = longDelta / longWidth;
//Calculate the proportion along vertically, from the top, the coordinate should be.
double latProportion = latDelta / latHeight;
//System.out.println(latProportion + " " + longProportion);
//Check which pixel dimension of our map is smaller. We use this to ensure that any rendered stuff retains its correct aspect ratio, and that everything is visible on screen.
int smallerDimension = Math.min(paneWidth, paneHeight);
//Check which metric dimension of our map is smaller. We use this to ensure that any rendered stuff retains its correct aspect ratio, and that everything is visible on screen.
double smallerDimension = Math.min(longitudeFactor, latitudeFactor);
//Calculate the x and y pixel coordinates.
//We take the complement of latProportion to flip it.
@ -58,12 +71,12 @@ public class GPSConverter {
int y = (int) (latProportion * smallerDimension);
//Because we try to maintain the correct aspect ratio, we will end up with "spare" pixels along the larger dimension (e.g., width 800, height 600, 200 extra pixels along width).
int extraPixels = Math.abs(paneWidth - paneHeight);
double extraDistance = Math.abs(longitudeFactor - latitudeFactor);
//We therefore "center" the coordinates along this larger dimension, by adding half of the extra pixels.
if (paneWidth > paneHeight) {
x += extraPixels / 2;
if (longitudeFactor > latitudeFactor) {
x += extraDistance / 2;
} else {
y += extraPixels / 2;
y += extraDistance / 2;
}

@ -14,7 +14,7 @@
<?import javafx.scene.layout.RowConstraints?>
<?import javafx.scene.text.Font?>
<AnchorPane fx:id="hostWrapper" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="600.0" prefWidth="780.0" xmlns="http://javafx.com/javafx/8.0.111" xmlns:fx="http://javafx.com/fxml/1" fx:controller="visualiser.Controllers.HostController">
<AnchorPane fx:id="hostWrapper" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="600.0" prefWidth="780.0" visible="false" xmlns="http://javafx.com/javafx/8.0.111" xmlns:fx="http://javafx.com/fxml/1" fx:controller="visualiser.Controllers.HostController">
<children>
<SplitPane fx:id="splitPane" dividerPositions="0.7724935732647815" layoutX="580.0" layoutY="129.0" prefHeight="160.0" prefWidth="200.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
<items>

@ -22,7 +22,7 @@
<?import javafx.scene.layout.StackPane?>
<?import javafx.scene.text.Font?>
<SplitPane fx:id="race" dividerPositions="1.0" prefHeight="431.0" prefWidth="610.0" visible="false" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0" xmlns="http://javafx.com/javafx/8.0.65" xmlns:fx="http://javafx.com/fxml/1" fx:controller="visualiser.Controllers.RaceController">
<SplitPane fx:id="racePane" dividerPositions="1.0" prefHeight="431.0" prefWidth="610.0" visible="false" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0" xmlns="http://javafx.com/javafx/8.0.65" xmlns:fx="http://javafx.com/fxml/1" fx:controller="visualiser.Controllers.RaceController">
<items>
<GridPane fx:id="canvasBase">
<columnConstraints>

Loading…
Cancel
Save