From 54baf4f884117f9d63ab2bb77fe6d81fd7a8eb63 Mon Sep 17 00:00:00 2001 From: Connor Taylor-Brown Date: Thu, 7 Sep 2017 23:22:27 +1200 Subject: [PATCH 1/8] Added scroll-wheel zooming - Race pane listens to scroll delta and sets View3D distance accordingly - GPSConverter has more applicable property names for infinite 3D #story[1190] --- .../Controllers/RaceController.java | 49 ++++++++----------- .../main/java/visualiser/layout/View3D.java | 23 ++------- .../java/visualiser/utils/GPSConverter.java | 41 +++++++++------- .../resources/visualiser/scenes/race.fxml | 2 +- 4 files changed, 48 insertions(+), 67 deletions(-) diff --git a/racevisionGame/src/main/java/visualiser/Controllers/RaceController.java b/racevisionGame/src/main/java/visualiser/Controllers/RaceController.java index cd450f18..4880fcaf 100644 --- a/racevisionGame/src/main/java/visualiser/Controllers/RaceController.java +++ b/racevisionGame/src/main/java/visualiser/Controllers/RaceController.java @@ -12,8 +12,8 @@ import javafx.scene.chart.LineChart; import javafx.scene.control.*; import javafx.scene.input.KeyCode; import javafx.scene.input.KeyEvent; +import javafx.scene.input.ScrollEvent; 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,8 +21,6 @@ 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; @@ -35,7 +33,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; @@ -75,7 +72,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. @@ -103,7 +100,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();} @@ -129,7 +126,7 @@ public class RaceController extends Controller { Optional result = alert.showAndWait(); if (result.get() == ButtonType.OK) { parent.endEvent(); - race.setVisible(false); + racePane.setVisible(false); App.app.showMainStage(App.getStage()); } } else { @@ -138,7 +135,7 @@ public class RaceController extends Controller { alert.setContentText("Do you wish to quit the race?"); Optional result = alert.showAndWait(); if (result.get() == ButtonType.OK) { - race.setVisible(false); + racePane.setVisible(false); App.app.showMainStage(App.getStage()); } } @@ -180,7 +177,7 @@ public class RaceController extends Controller { private void initialiseView3D(VisualiserRaceEvent race) { int scale = 1; - ObservableList subjects = FXCollections.observableArrayList(); + viewSubjects = FXCollections.observableArrayList(); URL asset = HostController.class.getClassLoader().getResource("assets/V1.2 Complete Boat.stl"); StlMeshImporter importer = new StlMeshImporter(); @@ -190,45 +187,35 @@ 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); + final GPSConverter gpsConverter = new GPSConverter(lat1, long1, lat2, long2, 450, 450); - view3D.setItems(subjects); + view3D.setItems(viewSubjects); canvasBase.getChildren().add(0, view3D); for(Mark mark: race.getVisualiserRaceState().getMarks()) { Subject3D subject = new Subject3D(new Sphere(5)); -// subject.setX(mark.getPosition().getLongitude() * scale); -// subject.setZ(mark.getPosition().getLatitude()* scale); subject.setX(gpsConverter.convertGPS(mark.getPosition()).getX() * scale); subject.setZ(gpsConverter.convertGPS(mark.getPosition()).getY() * scale); - subjects.add(subject); + viewSubjects.add(subject); } for(VisualiserBoat boat: race.getVisualiserRaceState().getBoats()) { MeshView mesh = new MeshView(importer.getImport()); Subject3D subject = new Subject3D(mesh); - subjects.add(subject); + viewSubjects.add(subject); AnimationTimer trackBoat = new AnimationTimer() { @Override public void handle(long now) { subject.setHeading(boat.getBearing().degrees()); -// subject.setHeading(0); -// subject.setX(boat.getPosition().getLongitude() * scale); -// subject.setZ(boat.getPosition().getLatitude()* scale); double x = gpsConverter.convertGPS(boat.getPosition()).getX() * scale; - //System.out.println(x); subject.setX(x); subject.setZ(gpsConverter.convertGPS(boat.getPosition()).getY() * scale); if(boat.getSourceID() == race.getVisualiserRaceState().getPlayerBoatID()) { @@ -240,6 +227,10 @@ public class RaceController extends Controller { trackBoat.start(); } view3D.updatePivot(new Translate(250, 0, 210)); + + racePane.setOnScroll(e -> { + view3D.updateDistance(e.getDeltaY()); + }); } @@ -422,7 +413,7 @@ public class RaceController extends Controller { initialiseRace(); //Display this controller. - race.setVisible(true); + racePane.setVisible(true); } /** @@ -430,7 +421,7 @@ public class RaceController extends Controller { * @param boats boats there are in the race. */ public void finishRace(ObservableList boats) { - race.setVisible(false); + racePane.setVisible(false); parent.enterFinish(boats); } @@ -471,7 +462,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()); @@ -493,10 +484,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); @@ -504,7 +495,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); diff --git a/racevisionGame/src/main/java/visualiser/layout/View3D.java b/racevisionGame/src/main/java/visualiser/layout/View3D.java index 95a6cfeb..118317f4 100644 --- a/racevisionGame/src/main/java/visualiser/layout/View3D.java +++ b/racevisionGame/src/main/java/visualiser/layout/View3D.java @@ -51,8 +51,6 @@ public class View3D extends Pane { */ private Rotate pitch; - private PerspectiveCamera camera; - /** * Default constructor for View3D. Sets up Scene and PerspectiveCamera. */ @@ -88,9 +86,7 @@ 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; } @@ -120,12 +116,6 @@ public class View3D extends Pane { 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 +124,10 @@ public class View3D extends Pane { this.distance.setZ(-distance); } + public void updateDistance(double delta) { + this.distance.setZ(this.distance.getZ() - delta); + } + /** * Set angle of camera from z-axis along ground * @param yaw in degrees @@ -149,13 +143,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); - } - } diff --git a/racevisionGame/src/main/java/visualiser/utils/GPSConverter.java b/racevisionGame/src/main/java/visualiser/utils/GPSConverter.java index 55a162b2..c7e4760a 100644 --- a/racevisionGame/src/main/java/visualiser/utils/GPSConverter.java +++ b/racevisionGame/src/main/java/visualiser/utils/GPSConverter.java @@ -7,20 +7,27 @@ import visualiser.model.GraphCoordinate; * Created by fwy13 on 7/09/17. */ 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){ + 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; + + public GPSConverter(double latTop, double longLeft, double latBottom, double longRight, double longitudeFactor, double latitudeFactor){ this.longRight = longRight; this.longLeft = longLeft; this.latBottom = latBottom; this.latTop = latTop; - this.paneWidth = (int)paneWidth; - this.paneHeight = (int)paneHeight; + this.longitudeFactor = (int)longitudeFactor; + this.latitudeFactor = (int)latitudeFactor; } /** @@ -46,11 +53,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 +63,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; } diff --git a/racevisionGame/src/main/resources/visualiser/scenes/race.fxml b/racevisionGame/src/main/resources/visualiser/scenes/race.fxml index bbc1c077..b6496743 100644 --- a/racevisionGame/src/main/resources/visualiser/scenes/race.fxml +++ b/racevisionGame/src/main/resources/visualiser/scenes/race.fxml @@ -22,7 +22,7 @@ - + From b59eca1a8b69834b90522e6d50a5797db3a3cc5a Mon Sep 17 00:00:00 2001 From: Connor Taylor-Brown Date: Fri, 8 Sep 2017 12:43:36 +1200 Subject: [PATCH 2/8] Added Subject3D selection to View3D - Changed how View3D is added to canvasBase to allow events to get through #story[1190] --- .../Controllers/RaceController.java | 16 ++++------- .../java/visualiser/layout/Subject3D.java | 3 --- .../main/java/visualiser/layout/View3D.java | 27 +++++++++++++++++-- 3 files changed, 30 insertions(+), 16 deletions(-) diff --git a/racevisionGame/src/main/java/visualiser/Controllers/RaceController.java b/racevisionGame/src/main/java/visualiser/Controllers/RaceController.java index 4880fcaf..0bbc2ad6 100644 --- a/racevisionGame/src/main/java/visualiser/Controllers/RaceController.java +++ b/racevisionGame/src/main/java/visualiser/Controllers/RaceController.java @@ -175,8 +175,6 @@ public class RaceController extends Controller { } private void initialiseView3D(VisualiserRaceEvent race) { - int scale = 1; - viewSubjects = FXCollections.observableArrayList(); URL asset = HostController.class.getClassLoader().getResource("assets/V1.2 Complete Boat.stl"); @@ -196,12 +194,12 @@ public class RaceController extends Controller { final GPSConverter gpsConverter = new GPSConverter(lat1, long1, lat2, long2, 450, 450); view3D.setItems(viewSubjects); - canvasBase.getChildren().add(0, view3D); + canvasBase.add(view3D, 0, 0); for(Mark mark: race.getVisualiserRaceState().getMarks()) { Subject3D subject = new Subject3D(new Sphere(5)); - subject.setX(gpsConverter.convertGPS(mark.getPosition()).getX() * scale); - subject.setZ(gpsConverter.convertGPS(mark.getPosition()).getY() * scale); + subject.setX(gpsConverter.convertGPS(mark.getPosition()).getX()); + subject.setZ(gpsConverter.convertGPS(mark.getPosition()).getY()); viewSubjects.add(subject); } @@ -215,9 +213,9 @@ public class RaceController extends Controller { @Override public void handle(long now) { subject.setHeading(boat.getBearing().degrees()); - double x = gpsConverter.convertGPS(boat.getPosition()).getX() * scale; + double x = gpsConverter.convertGPS(boat.getPosition()).getX(); subject.setX(x); - subject.setZ(gpsConverter.convertGPS(boat.getPosition()).getY() * scale); + subject.setZ(gpsConverter.convertGPS(boat.getPosition()).getY()); if(boat.getSourceID() == race.getVisualiserRaceState().getPlayerBoatID()) { //view3D.updatePivot(subject.getPosition()); } @@ -227,10 +225,6 @@ public class RaceController extends Controller { trackBoat.start(); } view3D.updatePivot(new Translate(250, 0, 210)); - - racePane.setOnScroll(e -> { - view3D.updateDistance(e.getDeltaY()); - }); } diff --git a/racevisionGame/src/main/java/visualiser/layout/Subject3D.java b/racevisionGame/src/main/java/visualiser/layout/Subject3D.java index adfd93ab..4bb6f6af 100644 --- a/racevisionGame/src/main/java/visualiser/layout/Subject3D.java +++ b/racevisionGame/src/main/java/visualiser/layout/Subject3D.java @@ -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() { diff --git a/racevisionGame/src/main/java/visualiser/layout/View3D.java b/racevisionGame/src/main/java/visualiser/layout/View3D.java index 118317f4..a981ca89 100644 --- a/racevisionGame/src/main/java/visualiser/layout/View3D.java +++ b/racevisionGame/src/main/java/visualiser/layout/View3D.java @@ -6,12 +6,17 @@ 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.Shape; 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 @@ -22,6 +27,10 @@ public class View3D extends Pane { * Observable list of renderable items */ private ObservableList items; + /** + * Map for selecting Subject3D from Shape3D + */ + private Map selectionMap; /** * Rendering container for shapes */ @@ -56,6 +65,7 @@ public class View3D extends Pane { */ public View3D() { world = new Group(); + selectionMap = new HashMap<>(); SubScene scene = new SubScene(world, 300, 300); scene.widthProperty().bind(this.widthProperty()); @@ -64,6 +74,13 @@ public class View3D extends Pane { scene.setCamera(buildCamera()); + scene.setOnMousePressed(e -> { + PickResult result = e.getPickResult(); + if(result != null && result.getIntersectedNode() != null && result.getIntersectedNode() instanceof Shape3D) { + System.out.println(selectionMap.get(result.getIntersectedNode())); + } + }); + this.getChildren().add(scene); } @@ -95,8 +112,14 @@ public class View3D extends Pane { this.items.addListener((ListChangeListener) 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); + } } } }); From 23eabed3ff5eb2e7d4c5959769b2cfc024a0ef04 Mon Sep 17 00:00:00 2001 From: Connor Taylor-Brown Date: Fri, 8 Sep 2017 12:58:06 +1200 Subject: [PATCH 3/8] View3D tracks selected boat at fixed distance, pitch, and relative yaw #story[1190] --- .../main/java/visualiser/Controllers/HostController.java | 2 +- .../src/main/java/visualiser/layout/Subject3D.java | 8 ++++---- .../src/main/java/visualiser/layout/View3D.java | 9 ++++++++- 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/racevisionGame/src/main/java/visualiser/Controllers/HostController.java b/racevisionGame/src/main/java/visualiser/Controllers/HostController.java index 2855a770..10bc9dbe 100644 --- a/racevisionGame/src/main/java/visualiser/Controllers/HostController.java +++ b/racevisionGame/src/main/java/visualiser/Controllers/HostController.java @@ -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(); diff --git a/racevisionGame/src/main/java/visualiser/layout/Subject3D.java b/racevisionGame/src/main/java/visualiser/layout/Subject3D.java index 4bb6f6af..af76f4f4 100644 --- a/racevisionGame/src/main/java/visualiser/layout/Subject3D.java +++ b/racevisionGame/src/main/java/visualiser/layout/Subject3D.java @@ -43,6 +43,10 @@ public class Subject3D { return this.position; } + public Rotate getHeading() { + return heading; + } + public void setX(double x) { position.setX(x); } @@ -55,10 +59,6 @@ public class Subject3D { position.setZ(z); } - public double getHeading() { - return heading.getAngle(); - } - public void setHeading(double angle) { heading.setAngle(angle); } diff --git a/racevisionGame/src/main/java/visualiser/layout/View3D.java b/racevisionGame/src/main/java/visualiser/layout/View3D.java index a981ca89..a6afb439 100644 --- a/racevisionGame/src/main/java/visualiser/layout/View3D.java +++ b/racevisionGame/src/main/java/visualiser/layout/View3D.java @@ -77,7 +77,14 @@ public class View3D extends Pane { scene.setOnMousePressed(e -> { PickResult result = e.getPickResult(); if(result != null && result.getIntersectedNode() != null && result.getIntersectedNode() instanceof Shape3D) { - System.out.println(selectionMap.get(result.getIntersectedNode())); + Subject3D target = selectionMap.get(result.getIntersectedNode()); + target.getPosition().xProperty().addListener((o, prev, curr) -> pivot.setX((double)curr)); + target.getPosition().yProperty().addListener((o, prev, curr) -> pivot.setY((double)curr)); + target.getPosition().zProperty().addListener((o, prev, curr) -> pivot.setZ((double)curr)); + target.getHeading().angleProperty().addListener((o, prev, curr) -> setYaw((double)curr)); + + this.setDistance(100); + this.setPitch(30); } }); From 1efec06bdcc8f332da2ab22e048e9a675cf7839f Mon Sep 17 00:00:00 2001 From: Connor Taylor-Brown Date: Fri, 8 Sep 2017 14:21:03 +1200 Subject: [PATCH 4/8] View3D can untrack objects and zoom in different modes - updateDistance zooms between 0 and infinity - Camera switches from third person to bird's eye when reaching a set distance - Only one subject can be tracked at a time #story[1190] --- .../Controllers/RaceController.java | 4 ++ .../main/java/visualiser/layout/View3D.java | 64 ++++++++++++++++--- 2 files changed, 59 insertions(+), 9 deletions(-) diff --git a/racevisionGame/src/main/java/visualiser/Controllers/RaceController.java b/racevisionGame/src/main/java/visualiser/Controllers/RaceController.java index 0bbc2ad6..4a8addb7 100644 --- a/racevisionGame/src/main/java/visualiser/Controllers/RaceController.java +++ b/racevisionGame/src/main/java/visualiser/Controllers/RaceController.java @@ -225,6 +225,10 @@ public class RaceController extends Controller { trackBoat.start(); } view3D.updatePivot(new Translate(250, 0, 210)); + + view3D.setOnScroll(e -> { + view3D.updateDistance(e.getDeltaY()); + }); } diff --git a/racevisionGame/src/main/java/visualiser/layout/View3D.java b/racevisionGame/src/main/java/visualiser/layout/View3D.java index a6afb439..3f867791 100644 --- a/racevisionGame/src/main/java/visualiser/layout/View3D.java +++ b/racevisionGame/src/main/java/visualiser/layout/View3D.java @@ -1,5 +1,7 @@ package visualiser.layout; +import javafx.beans.value.ChangeListener; +import javafx.beans.value.ObservableValue; import javafx.collections.ListChangeListener; import javafx.collections.ObservableList; import javafx.geometry.Point3D; @@ -31,6 +33,10 @@ public class View3D extends Pane { * Map for selecting Subject3D from Shape3D */ private Map selectionMap; + /** + * Subject tracked by camera + */ + private Subject3D target; /** * Rendering container for shapes */ @@ -60,12 +66,23 @@ public class View3D extends Pane { */ private Rotate pitch; + private ChangeListener pivotHeading = (o, prev, curr) -> yaw.setAngle((double)curr); + + private ChangeListener pivotX = (o, prev, curr) -> pivot.setX((double)curr); + + private ChangeListener pivotY = (o, prev, curr) -> pivot.setY((double)curr); + + private ChangeListener pivotZ = (o, prev, curr) -> pivot.setZ((double)curr); + + private double THIRD_PERSON_LIMIT = 100; + /** * Default constructor for View3D. Sets up Scene and PerspectiveCamera. */ public View3D() { world = new Group(); selectionMap = new HashMap<>(); + target = null; SubScene scene = new SubScene(world, 300, 300); scene.widthProperty().bind(this.widthProperty()); @@ -77,14 +94,7 @@ public class View3D extends Pane { scene.setOnMousePressed(e -> { PickResult result = e.getPickResult(); if(result != null && result.getIntersectedNode() != null && result.getIntersectedNode() instanceof Shape3D) { - Subject3D target = selectionMap.get(result.getIntersectedNode()); - target.getPosition().xProperty().addListener((o, prev, curr) -> pivot.setX((double)curr)); - target.getPosition().yProperty().addListener((o, prev, curr) -> pivot.setY((double)curr)); - target.getPosition().zProperty().addListener((o, prev, curr) -> pivot.setZ((double)curr)); - target.getHeading().angleProperty().addListener((o, prev, curr) -> setYaw((double)curr)); - - this.setDistance(100); - this.setPitch(30); + trackSubject(selectionMap.get(result.getIntersectedNode())); } }); @@ -132,6 +142,31 @@ public class View3D extends Pane { }); } + 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); + } + } + + 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; } @@ -155,7 +190,18 @@ public class View3D extends Pane { } public void updateDistance(double delta) { - this.distance.setZ(this.distance.getZ() - 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); + } } /** From 68f434b6b6c17ad38cad25044d904ef3886b1c11 Mon Sep 17 00:00:00 2001 From: Connor Taylor-Brown Date: Fri, 8 Sep 2017 15:49:53 +1200 Subject: [PATCH 5/8] Bound zooming action to zoom keys - View3D subject selection is no longer enabled by default - Added documentation #story[1190] --- .../Controllers/RaceController.java | 24 ++++---- .../gameController/Keys/ControlKey.java | 3 +- .../main/java/visualiser/layout/View3D.java | 57 ++++++++++++------- .../java/visualiser/utils/GPSConverter.java | 15 ++--- 4 files changed, 59 insertions(+), 40 deletions(-) diff --git a/racevisionGame/src/main/java/visualiser/Controllers/RaceController.java b/racevisionGame/src/main/java/visualiser/Controllers/RaceController.java index 4a8addb7..a7cf88b3 100644 --- a/racevisionGame/src/main/java/visualiser/Controllers/RaceController.java +++ b/racevisionGame/src/main/java/visualiser/Controllers/RaceController.java @@ -26,6 +26,7 @@ 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.Subject3D; import visualiser.layout.View3D; import visualiser.model.*; @@ -185,15 +186,12 @@ public class RaceController extends Controller { view3D.setDistance(1050); view3D.setYaw(0); view3D.setPitch(60); - 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(); - final GPSConverter gpsConverter = new GPSConverter(lat1, long1, lat2, long2, 450, 450); + RaceDataSource raceData = visualiserRace.getVisualiserRaceState().getRaceDataSource(); + final GPSConverter gpsConverter = new GPSConverter(raceData, 450, 450); view3D.setItems(viewSubjects); + view3D.enableTracking(); canvasBase.add(view3D, 0, 0); for(Mark mark: race.getVisualiserRaceState().getMarks()) { @@ -213,13 +211,8 @@ public class RaceController extends Controller { @Override public void handle(long now) { subject.setHeading(boat.getBearing().degrees()); - double x = gpsConverter.convertGPS(boat.getPosition()).getX(); - subject.setX(x); + 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(); @@ -229,6 +222,13 @@ public class RaceController extends Controller { view3D.setOnScroll(e -> { view3D.updateDistance(e.getDeltaY()); }); + + racePane.addEventFilter(KeyEvent.KEY_PRESSED, e -> { + switch(keyFactory.getKey(e.getCode().toString()).toString()) { + case "Zoom In": view3D.updateDistance(-10); break; + case "Zoom Out": view3D.updateDistance(10); break; + } + }); } diff --git a/racevisionGame/src/main/java/visualiser/gameController/Keys/ControlKey.java b/racevisionGame/src/main/java/visualiser/gameController/Keys/ControlKey.java index dd489f73..ce4b341e 100644 --- a/racevisionGame/src/main/java/visualiser/gameController/Keys/ControlKey.java +++ b/racevisionGame/src/main/java/visualiser/gameController/Keys/ControlKey.java @@ -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 diff --git a/racevisionGame/src/main/java/visualiser/layout/View3D.java b/racevisionGame/src/main/java/visualiser/layout/View3D.java index 3f867791..db5ee85b 100644 --- a/racevisionGame/src/main/java/visualiser/layout/View3D.java +++ b/racevisionGame/src/main/java/visualiser/layout/View3D.java @@ -1,17 +1,14 @@ package visualiser.layout; import javafx.beans.value.ChangeListener; -import javafx.beans.value.ObservableValue; 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.Shape; import javafx.scene.shape.Shape3D; import javafx.scene.transform.Rotate; import javafx.scene.transform.Translate; @@ -25,6 +22,10 @@ import java.util.Map; * 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 */ @@ -65,39 +66,42 @@ public class View3D extends Pane { * Angle between ground plane and camera direction */ private Rotate pitch; - + /** + * Single listener for subject heading changes + */ private ChangeListener pivotHeading = (o, prev, curr) -> yaw.setAngle((double)curr); - + /** + * Single listener for subject position (x) changes + */ private ChangeListener pivotX = (o, prev, curr) -> pivot.setX((double)curr); - + /** + * Single listener for subject position (y) changes + */ private ChangeListener pivotY = (o, prev, curr) -> pivot.setY((double)curr); - + /** + * Single listener for subject position (z) changes + */ private ChangeListener 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(); - selectionMap = new HashMap<>(); - target = null; + 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)); scene.setCamera(buildCamera()); - scene.setOnMousePressed(e -> { - PickResult result = e.getPickResult(); - if(result != null && result.getIntersectedNode() != null && result.getIntersectedNode() instanceof Shape3D) { - trackSubject(selectionMap.get(result.getIntersectedNode())); - } - }); - this.getChildren().add(scene); } @@ -142,6 +146,15 @@ public class View3D extends Pane { }); } + 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())); + } + }); + } + private void untrackSubject() { if(target != null) { target.getPosition().xProperty().removeListener(pivotX); @@ -189,6 +202,12 @@ 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; diff --git a/racevisionGame/src/main/java/visualiser/utils/GPSConverter.java b/racevisionGame/src/main/java/visualiser/utils/GPSConverter.java index c7e4760a..49ef7bcb 100644 --- a/racevisionGame/src/main/java/visualiser/utils/GPSConverter.java +++ b/racevisionGame/src/main/java/visualiser/utils/GPSConverter.java @@ -1,5 +1,6 @@ package visualiser.utils; +import shared.dataInput.RaceDataSource; import shared.model.GPSCoordinate; import visualiser.model.GraphCoordinate; @@ -21,13 +22,13 @@ public class GPSConverter { */ private double latitudeFactor; - public GPSConverter(double latTop, double longLeft, double latBottom, double longRight, double longitudeFactor, double latitudeFactor){ - this.longRight = longRight; - this.longLeft = longLeft; - this.latBottom = latBottom; - this.latTop = latTop; - this.longitudeFactor = (int)longitudeFactor; - this.latitudeFactor = (int)latitudeFactor; + 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; } /** From b034a452b60503dd9696f7968e7fefff59c2e7c0 Mon Sep 17 00:00:00 2001 From: Connor Taylor-Brown Date: Fri, 8 Sep 2017 16:30:03 +1200 Subject: [PATCH 6/8] Fixed problem with race pane registering non-control keys #story[1190] --- .../java/visualiser/Controllers/RaceController.java | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/racevisionGame/src/main/java/visualiser/Controllers/RaceController.java b/racevisionGame/src/main/java/visualiser/Controllers/RaceController.java index a7cf88b3..35e20cb7 100644 --- a/racevisionGame/src/main/java/visualiser/Controllers/RaceController.java +++ b/racevisionGame/src/main/java/visualiser/Controllers/RaceController.java @@ -224,9 +224,16 @@ public class RaceController extends Controller { }); racePane.addEventFilter(KeyEvent.KEY_PRESSED, e -> { - switch(keyFactory.getKey(e.getCode().toString()).toString()) { - case "Zoom In": view3D.updateDistance(-10); break; - case "Zoom Out": view3D.updateDistance(10); break; + 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; + } } }); } From 0c9e55093cacaf5dcb1abc9686d88aa66dfe24f7 Mon Sep 17 00:00:00 2001 From: Connor Taylor-Brown Date: Fri, 8 Sep 2017 17:11:21 +1200 Subject: [PATCH 7/8] Set lobby anchor pane to invisible to allow races to start --- .../src/main/resources/visualiser/scenes/hostlobby.fxml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/racevisionGame/src/main/resources/visualiser/scenes/hostlobby.fxml b/racevisionGame/src/main/resources/visualiser/scenes/hostlobby.fxml index c4198bf3..044e7078 100644 --- a/racevisionGame/src/main/resources/visualiser/scenes/hostlobby.fxml +++ b/racevisionGame/src/main/resources/visualiser/scenes/hostlobby.fxml @@ -14,7 +14,7 @@ - + From 93883ee8b7e9bca352f438ed46f1566039013661 Mon Sep 17 00:00:00 2001 From: Connor Taylor-Brown Date: Fri, 8 Sep 2017 23:48:47 +1200 Subject: [PATCH 8/8] Added more descriptive documentation to new classes and functionality #story[1190] --- .../Controllers/RaceController.java | 17 ++++++++++++----- .../main/java/visualiser/layout/View3D.java | 19 +++++++++++++++++++ .../java/visualiser/utils/GPSConverter.java | 11 +++++++++-- 3 files changed, 40 insertions(+), 7 deletions(-) diff --git a/racevisionGame/src/main/java/visualiser/Controllers/RaceController.java b/racevisionGame/src/main/java/visualiser/Controllers/RaceController.java index 35e20cb7..e864993d 100644 --- a/racevisionGame/src/main/java/visualiser/Controllers/RaceController.java +++ b/racevisionGame/src/main/java/visualiser/Controllers/RaceController.java @@ -178,35 +178,39 @@ public class RaceController extends Controller { private void initialiseView3D(VisualiserRaceEvent race) { viewSubjects = FXCollections.observableArrayList(); + // 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 view3D = new View3D(); view3D.setDistance(1050); view3D.setYaw(0); view3D.setPitch(60); + view3D.enableTracking(); + canvasBase.add(view3D, 0, 0); + // Set up projection from GPS to view RaceDataSource raceData = visualiserRace.getVisualiserRaceState().getRaceDataSource(); final GPSConverter gpsConverter = new GPSConverter(raceData, 450, 450); view3D.setItems(viewSubjects); - view3D.enableTracking(); - canvasBase.add(view3D, 0, 0); - + // Position and add each mark to view for(Mark mark: race.getVisualiserRaceState().getMarks()) { - Subject3D subject = new Subject3D(new Sphere(5)); + Subject3D subject = new Subject3D(new Sphere(2)); subject.setX(gpsConverter.convertGPS(mark.getPosition()).getX()); subject.setZ(gpsConverter.convertGPS(mark.getPosition()).getY()); viewSubjects.add(subject); } - + // Position and add each boat to view for(VisualiserBoat boat: race.getVisualiserRaceState().getBoats()) { MeshView mesh = new MeshView(importer.getImport()); Subject3D subject = new Subject3D(mesh); viewSubjects.add(subject); + // Track this boat's movement with the new subject AnimationTimer trackBoat = new AnimationTimer() { @Override public void handle(long now) { @@ -217,12 +221,15 @@ public class RaceController extends Controller { }; 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) { diff --git a/racevisionGame/src/main/java/visualiser/layout/View3D.java b/racevisionGame/src/main/java/visualiser/layout/View3D.java index db5ee85b..27fe6086 100644 --- a/racevisionGame/src/main/java/visualiser/layout/View3D.java +++ b/racevisionGame/src/main/java/visualiser/layout/View3D.java @@ -128,6 +128,11 @@ public class View3D extends Pane { 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 items) { this.items = items; this.items.addListener((ListChangeListener) c -> { @@ -146,6 +151,9 @@ public class View3D extends Pane { }); } + /** + * Intercept mouse clicks on subjects in view. The applied listener cannot be removed. + */ public void enableTracking() { scene.setOnMousePressed(e -> { PickResult result = e.getPickResult(); @@ -155,6 +163,9 @@ public class View3D extends Pane { }); } + /** + * Stop camera from following the last selected subject + */ private void untrackSubject() { if(target != null) { target.getPosition().xProperty().removeListener(pivotX); @@ -164,6 +175,10 @@ public class View3D extends Pane { } } + /** + * Set camera to follow the selected subject + * @param subject to track + */ private void trackSubject(Subject3D subject) { untrackSubject(); target = subject; @@ -188,6 +203,10 @@ 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()); diff --git a/racevisionGame/src/main/java/visualiser/utils/GPSConverter.java b/racevisionGame/src/main/java/visualiser/utils/GPSConverter.java index 49ef7bcb..22dd937f 100644 --- a/racevisionGame/src/main/java/visualiser/utils/GPSConverter.java +++ b/racevisionGame/src/main/java/visualiser/utils/GPSConverter.java @@ -5,14 +5,15 @@ 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 { private double longRight; private double longLeft; private double latBottom; private double latTop; - /** * Conversion factor from longitude to view units */ @@ -22,6 +23,12 @@ public class GPSConverter { */ 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();