From 51747e2d13ce5ca10234978f2e4d3a80b19bbe98 Mon Sep 17 00:00:00 2001 From: Calum Date: Tue, 26 Sep 2017 00:55:28 +1300 Subject: [PATCH] Refactored the 2D and 3D game view class setups. Made scaling more logical. #refactor #story[1275] --- .../java/seng302/gameServer/GameState.java | 28 - .../gameServer/ServerToClientThread.java | 2 +- src/main/java/seng302/model/ScaledPoint.java | 122 +++++ .../java/seng302/utilities/XMLParser.java | 6 +- .../java/seng302/visualiser/GameView.java | 499 +----------------- .../java/seng302/visualiser/GameView3D.java | 221 +------- .../java/seng302/visualiser/MapMaker.java | 15 +- .../java/seng302/visualiser/MapPreview.java | 283 ++++++++++ .../controllers/LobbyController.java | 14 +- .../controllers/RaceViewController.java | 191 +------ src/main/resources/maps/horseshoe.xml | 48 +- 11 files changed, 498 insertions(+), 931 deletions(-) create mode 100644 src/main/java/seng302/model/ScaledPoint.java create mode 100644 src/main/java/seng302/visualiser/MapPreview.java diff --git a/src/main/java/seng302/gameServer/GameState.java b/src/main/java/seng302/gameServer/GameState.java index 790817e0..6eccd2ca 100644 --- a/src/main/java/seng302/gameServer/GameState.java +++ b/src/main/java/seng302/gameServer/GameState.java @@ -9,17 +9,7 @@ import java.util.List; import java.util.Map; import java.util.Random; import java.util.Set; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Random; -import java.util.Set; import javafx.scene.paint.Color; -import javax.xml.parsers.DocumentBuilder; -import javax.xml.parsers.DocumentBuilderFactory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import seng302.gameServer.messages.BoatAction; @@ -37,23 +27,6 @@ import seng302.model.Limit; import seng302.model.Player; import seng302.model.PolarTable; import seng302.model.ServerYacht; -import org.w3c.dom.Document; -import org.xml.sax.InputSource; -import seng302.gameServer.messages.BoatAction; -import seng302.gameServer.messages.BoatStatus; -import seng302.gameServer.messages.ChatterMessage; -import seng302.gameServer.messages.CustomizeRequestType; -import seng302.gameServer.messages.MarkRoundingMessage; -import seng302.gameServer.messages.MarkType; -import seng302.gameServer.messages.Message; -import seng302.gameServer.messages.RoundingBoatStatus; -import seng302.gameServer.messages.YachtEventCodeMessage; -import seng302.gameServer.messages.YachtEventType; -import seng302.model.GeoPoint; -import seng302.model.Limit; -import seng302.model.Player; -import seng302.model.PolarTable; -import seng302.model.ServerYacht; import seng302.model.mark.CompoundMark; import seng302.model.mark.Mark; import seng302.model.mark.MarkOrder; @@ -61,7 +34,6 @@ import seng302.model.stream.xml.parser.RaceXMLData; import seng302.model.token.Token; import seng302.model.token.TokenType; import seng302.utilities.GeoUtility; -import seng302.utilities.XMLParser; import seng302.visualiser.fxObjects.assets_3D.BoatMeshType; /** diff --git a/src/main/java/seng302/gameServer/ServerToClientThread.java b/src/main/java/seng302/gameServer/ServerToClientThread.java index 29c892a3..0df0e0fc 100644 --- a/src/main/java/seng302/gameServer/ServerToClientThread.java +++ b/src/main/java/seng302/gameServer/ServerToClientThread.java @@ -102,7 +102,7 @@ public class ServerToClientThread implements Runnable { } private void setUpPlayer(){ - String fName = "Player " + GameState.getNumberOfPlayers().toString(); + String fName = "Player" + GameState.getNumberOfPlayers().toString(); String lName = ""; ServerYacht yacht = new ServerYacht( BoatMeshType.DINGHY, sourceId, sourceId.toString(), fName, fName + " " + lName, "NZ" diff --git a/src/main/java/seng302/model/ScaledPoint.java b/src/main/java/seng302/model/ScaledPoint.java new file mode 100644 index 00000000..14cb490c --- /dev/null +++ b/src/main/java/seng302/model/ScaledPoint.java @@ -0,0 +1,122 @@ +package seng302.model; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; +import javafx.geometry.Point2D; +import seng302.utilities.GeoUtility; + +/** + * Created by cir27 on 26/09/17. + */ +public class ScaledPoint extends GeoPoint { + + public enum ScaleDirection { + HORIZONTAL, + VERTICAL + } + + private double x, y, scaleFactor; + private ScaleDirection scaleDirection; + + private ScaledPoint(double lat, double lng, double x, double y, double scaleFactor, ScaleDirection direction) { + super(lat, lng); + this.x = x; + this.y = y; + this.scaleFactor = scaleFactor; + this.scaleDirection = direction; + } + + public double getX() { + return x; + } + + public double getY() { + return y; + } + + public double getScaleFactor() { + return scaleFactor; + } + + public ScaleDirection getScaleDirection() { + return scaleDirection; + } + + public Point2D findScaledXY(GeoPoint unscaled) { + return findScaledXY(unscaled.getLat(), unscaled.getLng()); + } + + public Point2D findScaledXY(double unscaledLat, double unscaledLon) { + double distanceFromReference; + double angleFromReference; + double xReference = this.getX(); + double yReference = this.getY(); + + angleFromReference = GeoUtility.getBearingRad( + this, new GeoPoint(unscaledLat, unscaledLon) + ); + distanceFromReference = GeoUtility.getDistance( + this, new GeoPoint(unscaledLat, unscaledLon) + ); + if (angleFromReference >= 0 && angleFromReference <= Math.PI / 2) { + xReference += scaleFactor * Math.sin(angleFromReference) * distanceFromReference; + yReference -= scaleFactor * Math.cos(angleFromReference) * distanceFromReference; + } else if (angleFromReference >= 0) { + angleFromReference = angleFromReference - Math.PI / 2; + xReference += scaleFactor * Math.cos(angleFromReference) * distanceFromReference; + yReference += scaleFactor * Math.sin(angleFromReference) * distanceFromReference; + } else if (angleFromReference < 0 && angleFromReference >= -Math.PI / 2) { + angleFromReference = Math.abs(angleFromReference); + xReference -= scaleFactor * Math.sin(angleFromReference) * distanceFromReference; + yReference -= scaleFactor * Math.cos(angleFromReference) * distanceFromReference; + } else { + angleFromReference = Math.abs(angleFromReference) - Math.PI / 2; + xReference -= scaleFactor * Math.cos(angleFromReference) * distanceFromReference; + yReference += scaleFactor * Math.sin(angleFromReference) * distanceFromReference; + } + return new Point2D(xReference, yReference); + } + + public static ScaledPoint makeScaledPoint(double width, double height, + List points, boolean centered) { + + double referencePointX, referencePointY, scaleFactor, lat, lng; + ScaleDirection scaleDirection; + points = new ArrayList<>(points); + points.sort(Comparator.comparingDouble(GeoPoint::getLat)); + GeoPoint minLatPoint = points.get(0); + GeoPoint maxLatPoint = points.get(points.size() - 1); + + points.sort(Comparator.comparingDouble(GeoPoint::getLng)); + GeoPoint minLonPoint = points.get(0); + GeoPoint maxLonPoint = points.get(points.size() - 1); + + referencePointX = centered ? 0 : width / 2; + referencePointY = centered ? 0 : height / 2; + + lat = (maxLatPoint.getLat() - minLatPoint.getLat()) / 2 + minLatPoint.getLat(); + lng = (maxLonPoint.getLng() - minLonPoint.getLng()) / 2 + minLonPoint.getLng(); + + GeoPoint ref = new GeoPoint(lat, lng); + + double vertDistance = GeoUtility.getDistance( + ref, new GeoPoint(ref.getLat(), maxLonPoint.getLng()) + ) * 2.1; + + double horiDistance = GeoUtility.getDistance( + ref, new GeoPoint(maxLatPoint.getLat(), ref.getLng()) + ) * 2.1; + + double vertScale = height / vertDistance; + + if (horiDistance * vertScale > width) { + scaleFactor = width / horiDistance; + scaleDirection = ScaleDirection.HORIZONTAL; + } else { + scaleFactor = vertScale; + scaleDirection = ScaleDirection.VERTICAL; + } + return new ScaledPoint(lat, lng, referencePointX, referencePointY, scaleFactor, scaleDirection); + } +} diff --git a/src/main/java/seng302/utilities/XMLParser.java b/src/main/java/seng302/utilities/XMLParser.java index c5aa0edc..d9dbeef6 100644 --- a/src/main/java/seng302/utilities/XMLParser.java +++ b/src/main/java/seng302/utilities/XMLParser.java @@ -416,9 +416,13 @@ public class XMLParser { for (int j = 0; j < segmentList.getLength(); j++) { Node corner = segmentList.item(j); if (corner.getNodeName().equals("Corner")) { + String rounding = XMLParser.getNodeAttributeString(corner, "Rounding"); + rounding = //Converting "P" to "Port" and "S" to "Stbd" + rounding.equals("P") ? "Port" : + rounding.equals("S") ? "Stbd" : rounding; course.add(new Corner( seqID++, XMLParser.getNodeAttributeInt(corner, "CompoundMarkID"), - XMLParser.getNodeAttributeString(corner, "Rounding"), 3 + rounding, 3 )); } } diff --git a/src/main/java/seng302/visualiser/GameView.java b/src/main/java/seng302/visualiser/GameView.java index 2eadfdf3..e0d62a5d 100644 --- a/src/main/java/seng302/visualiser/GameView.java +++ b/src/main/java/seng302/visualiser/GameView.java @@ -1,500 +1,31 @@ package seng302.visualiser; import java.util.ArrayList; -import java.util.Comparator; -import java.util.HashMap; import java.util.List; -import java.util.Map; -import javafx.application.Platform; -import javafx.beans.value.ChangeListener; -import javafx.collections.ObservableList; -import javafx.geometry.Point2D; import javafx.scene.Group; import javafx.scene.Node; -import javafx.scene.image.ImageView; -import javafx.scene.layout.Pane; -import javafx.scene.paint.Color; -import javafx.scene.paint.Paint; -import javafx.scene.shape.Polygon; -import seng302.gameServer.messages.RoundingSide; -import seng302.model.GeoPoint; import seng302.model.Limit; +import seng302.model.ScaledPoint; import seng302.model.mark.CompoundMark; import seng302.model.mark.Corner; -import seng302.model.mark.Mark; -import seng302.utilities.GeoUtility; -import seng302.visualiser.fxObjects.MarkArrowFactory; -import seng302.visualiser.fxObjects.assets_2D.CourseBoundary; -import seng302.visualiser.fxObjects.assets_2D.Gate; -import seng302.visualiser.fxObjects.assets_2D.Marker2D; /** - * Created by cir27 on 20/07/17. + * Created by cir27 on 26/09/17. */ -public class GameView extends Pane { +public abstract class GameView { - private double bufferSize = 0; - private double horizontalBuffer = 0; + double canvasWidth, canvasHeight; + ScaledPoint scaledPoint; - private double canvasWidth = 1100; - private double canvasHeight = 920; - private boolean horizontalInversion = false; + List borderPoints; + Group gameObjects = new Group(); + Group markers = new Group(); + Group tokens = new Group(); + List course = new ArrayList<>(); + List compoundMarks = new ArrayList<>(); + List courseOrder = new ArrayList<>(); - private double distanceScaleFactor; - private ScaleDirection scaleDirection; - private GeoPoint minLatPoint, minLonPoint, maxLatPoint, maxLonPoint; - private double referencePointX, referencePointY; - - private Polygon raceBorder = new CourseBoundary(); - - /* Note that if either of these is null then values for it have not been added and the other - should be used as the limits of the map. */ - private List borderPoints; - private Map markerObjects; - - private ObservableList gameObjects; - private Group markers = new Group(); - private Group tokens = new Group(); - private List orderedMarks = new ArrayList<>(); - private List compoundMarks = new ArrayList<>(); - private List courseOrder = new ArrayList<>(); - - private ChangeListener heightChangeListener; - - private ImageView mapImage = new ImageView(); - - private enum ScaleDirection { - HORIZONTAL, - VERTICAL - } - - public GameView (List marks, List course, List border) { -// updateBorder(border); -// updateCourse(marks, orderedMarks); - this.compoundMarks = marks; - this.courseOrder = course; - this.borderPoints = border; - gameObjects = this.getChildren(); - gameObjects.addAll(mapImage, raceBorder, markers, tokens); - this.parentProperty().addListener((obs, old, parent) -> { - if (parent != null) { - canvasWidth = parent.prefWidth(1); - canvasHeight = parent.prefHeight(1); - updateBorder(borderPoints); - updateCourse(compoundMarks, courseOrder); - } - }); - } - - public void setSize(double width, double height) { - canvasHeight = height; - canvasWidth = width; - updateBorder(borderPoints); - updateCourse(compoundMarks, courseOrder); - } - - /** - * Adds a orderedMarks to the GameView. The view is scaled accordingly unless a border is set in which - * case the orderedMarks is added relative ot the border. - * - * @param newCourse the mark objects that make up the orderedMarks. - * @param sequence The sequence the marks travel through - */ - public void updateCourse(List newCourse, List sequence) { - - if (newCourse.size() == 0) { - return; - } - compoundMarks = newCourse; - markerObjects = new HashMap<>(); - courseOrder = sequence; - - for (Corner corner : courseOrder) { //Makes orderedMarks out of all compound marks. - for (CompoundMark compoundMark : newCourse) { - if (corner.getCompoundMarkID() == compoundMark.getId()) { - orderedMarks.add(compoundMark); - } - } - } - - // TODO: 16/08/17 Updating mark roundings here. It should not happen here. Nor should it be done this way. - for (Corner corner : sequence){ - CompoundMark compoundMark = orderedMarks.get(corner.getSeqID() - 1); - compoundMark.setRoundingSide( - RoundingSide.getRoundingSide(corner.getRounding()) - ); - } - - final List gates = new ArrayList<>(); - Paint colour = Color.BLACK; - //Creates new markers - for (CompoundMark cMark : newCourse) { - //Set start and end colour - if (cMark.getId() == sequence.get(0).getCompoundMarkID()) { - colour = Color.GREEN; - } else if (cMark.getId() == sequence.get(sequence.size() - 1).getCompoundMarkID()) { - colour = Color.RED; - } - //Create mark dots - for (Mark mark : cMark.getMarks()) { - makeAndBindMarker(mark, colour); - } - //Create gate line - if (cMark.isGate()) { - for (int i = 1; i < cMark.getMarks().size(); i++) { - gates.add( - makeAndBindGate( - markerObjects.get(cMark.getSubMark(i)), - markerObjects.get(cMark.getSubMark(i + 1)), - colour - ) - ); - } - } - colour = Color.BLACK; - } - - createMarkArrows(sequence); - - //Scale race to markers if there is no border. - if (borderPoints == null) { - rescaleRace(new ArrayList<>(markerObjects.keySet())); - } - //Move the Markers to initial position. - markerObjects.forEach(((mark, marker2D) -> { - Point2D p2d = findScaledXY(mark.getLat(), mark.getLng()); - marker2D.setLayoutX(p2d.getX()); - marker2D.setLayoutY(p2d.getY()); - })); - Platform.runLater(() -> { - markers.getChildren().clear(); - markers.getChildren().addAll(gates); - markers.getChildren().addAll(markerObjects.values()); - }); - } - - /** - * Calculates all the data needed for to create mark arrows. Requires that a orderedMarks has been - * added to the gameview. - * @param sequence The order in which marks are traversed. - */ - private void createMarkArrows (List sequence) { - for (int i=1; i < sequence.size()-1; i++) { //General case. - double averageLat = 0; - double averageLng = 0; - int numMarks = orderedMarks.get(i-1).getMarks().size(); - for (Mark mark : orderedMarks.get(i-1).getMarks()) { - averageLat += mark.getLat(); - averageLng += mark.getLng(); - } - GeoPoint lastMarkAv = new GeoPoint(averageLat / numMarks, averageLng / numMarks); - numMarks = orderedMarks.get(i+1).getMarks().size(); - averageLat = 0; - averageLng = 0; - for (Mark mark : orderedMarks.get(i+1).getMarks()) { - averageLat += mark.getLat(); - averageLng += mark.getLng(); - } - GeoPoint nextMarkAv = new GeoPoint(averageLat / numMarks, averageLng / numMarks); - // TODO: 16/08/17 This comparison doesn't need to exist but the alternative is to user server enum client side. - for (Mark mark : orderedMarks.get(i).getMarks()) { - markerObjects.get(mark).addArrows( - mark.getRoundingSide() == RoundingSide.STARBOARD ? MarkArrowFactory.RoundingSide.STARBOARD : MarkArrowFactory.RoundingSide.PORT, - GeoUtility.getBearing(lastMarkAv, mark), - GeoUtility.getBearing(mark, nextMarkAv) - ); - } - } - createStartLineArrows(); - createFinishLineArrows(); - } - - private void createStartLineArrows () { - double averageLat = 0; - double averageLng = 0; - int numMarks = 0; - for (Mark mark : orderedMarks.get(1).getMarks()) { - numMarks += 1; - averageLat += mark.getLat(); - averageLng += mark.getLng(); - } - GeoPoint firstMarkAv = new GeoPoint(averageLat / numMarks, averageLng / numMarks); - for (Mark mark : orderedMarks.get(0).getMarks()) { - markerObjects.get(mark).addArrows( - mark.getRoundingSide() == RoundingSide.STARBOARD ? MarkArrowFactory.RoundingSide.STARBOARD : MarkArrowFactory.RoundingSide.PORT, - 0d, //90 - GeoUtility.getBearing(mark, firstMarkAv) - ); - } - } - - private void createFinishLineArrows () { - double numMarks = 0; - double averageLat = 0; - double averageLng = 0; - for (Mark mark : orderedMarks.get(orderedMarks.size()-2).getMarks()) { - numMarks += 1; - averageLat += mark.getLat(); - averageLng += mark.getLng(); - } - GeoPoint secondToLastMarkAv = new GeoPoint(averageLat / numMarks, averageLng / numMarks); - for (Mark mark : orderedMarks.get(orderedMarks.size()-1).getMarks()) { - markerObjects.get(mark).addArrows( - mark.getRoundingSide() == RoundingSide.STARBOARD ? MarkArrowFactory.RoundingSide.STARBOARD : MarkArrowFactory.RoundingSide.PORT, - GeoUtility.getBearing(secondToLastMarkAv, mark), - GeoUtility.getBearing(mark, mark) - ); - } - } - - /** - * Creates a new Marker and binds it's position to the given Mark. - * - * @param observableMark The mark to bind the marker to. - * @param colour The desired colour of the mark - */ - private void makeAndBindMarker(Mark observableMark, Paint colour) { - Marker2D marker2D = new Marker2D(colour); -// marker.addArrows(MarkArrowFactory.RoundingSide.PORT, ThreadLocalRandom.current().nextDouble(91, 180), ThreadLocalRandom.current().nextDouble(1, 90)); - markerObjects.put(observableMark, marker2D); - observableMark.addPositionListener((mark, lat, lon) -> { - Point2D p2d = findScaledXY(lat, lon); - markerObjects.get(mark).setLayoutX(p2d.getX()); - markerObjects.get(mark).setLayoutY(p2d.getY()); - }); - } - - /** - * Creates a new gate connecting the given marks. - * - * @param m1 The first Mark of the gate. - * @param m2 The second Mark of the gate. - * @param colour The desired colour of the gate. - * @return the new gate. - */ - private Gate makeAndBindGate(Marker2D m1, Marker2D m2, Paint colour) { - Gate gate = new Gate(colour); - gate.startXProperty().bind( - m1.layoutXProperty() - ); - gate.startYProperty().bind( - m1.layoutYProperty() - ); - gate.endXProperty().bind( - m2.layoutXProperty() - ); - gate.endYProperty().bind( - m2.layoutYProperty() - ); - return gate; - } - - /** - * Adds a border to the GameView and rescales to the size of the border, does not rescale if a - * border already exists. Assumes the border is larger than the orderedMarks. - * - * @param border the race border to be drawn. - */ - public void updateBorder(List border) { - if (border.size() == 0) { - return; - } - borderPoints = border; - rescaleRace(borderPoints); - - List boundaryPoints = new ArrayList<>(); - for (Limit limit : border) { - Point2D location = findScaledXY(limit.getLat(), limit.getLng()); - boundaryPoints.add(location.getX()); - boundaryPoints.add(location.getY()); - } - raceBorder.getPoints().setAll(boundaryPoints); - } - - /** - * Rescales the race to the size of the window. - * - * @param limitingCoordinates the set of geo points that contains the extremities of the race. - */ - public void rescaleRace(List limitingCoordinates) { - //Check is called once to avoid unnecessarily change the orderedMarks limits once the race is running - findMinMaxPoint(limitingCoordinates); - double minLonToMaxLon = scaleRaceExtremities(); - calculateReferencePointLocation(); - } - - /** - * Sets the class variables minLatPoint, maxLatPoint, minLonPoint, maxLonPoint to the point with - * the leftmost point, rightmost point, southern most point and northern most point - * respectively. - */ - private void findMinMaxPoint(List points) { - List sortedPoints = new ArrayList<>(points); - sortedPoints.sort(Comparator.comparingDouble(GeoPoint::getLat)); - minLatPoint = new GeoPoint(sortedPoints.get(0).getLat(), sortedPoints.get(0).getLng()); - GeoPoint maxLat = sortedPoints.get(sortedPoints.size() - 1); - maxLatPoint = new GeoPoint(maxLat.getLat(), maxLat.getLng()); - - sortedPoints.sort(Comparator.comparingDouble(GeoPoint::getLng)); - minLonPoint = new GeoPoint(sortedPoints.get(0).getLat(), sortedPoints.get(0).getLng()); - GeoPoint maxLon = sortedPoints.get(sortedPoints.size() - 1); - maxLonPoint = new GeoPoint(maxLon.getLat(), maxLon.getLng()); -// if (maxLonPoint.getLng() - minLonPoint.getLng() > 180) { -// horizontalInversion = true; -// } - } - - private void calculateReferencePointLocation() { - - referencePointX = canvasWidth / 2; - referencePointY = canvasHeight / 2; - GeoPoint ref = new GeoPoint( - (maxLatPoint.getLat() - minLatPoint.getLat()) / 2 + minLatPoint.getLat(), - (maxLonPoint.getLng() - minLonPoint.getLng()) / 2 + minLonPoint.getLng() - ); - - -// double vertAngle = Math.abs( -// GeoUtility.getBearingRad(minLatPoint, maxLatPoint) -// ); - - double vertDistance = GeoUtility.getDistance( - ref, new GeoPoint(ref.getLat(), maxLonPoint.getLng()) - ) * 2.1; //2.1 allows for empty space around the map. - - double horiDistance = GeoUtility.getDistance( - ref, new GeoPoint(maxLatPoint.getLat(), ref.getLng()) - ) * 2.1; - - double vertScale = canvasHeight / vertDistance; - - if (horiDistance * vertScale > canvasWidth) { - distanceScaleFactor = canvasWidth / horiDistance; - scaleDirection = ScaleDirection.HORIZONTAL; - } else { - distanceScaleFactor = vertScale; - scaleDirection = ScaleDirection.VERTICAL; - } - - minLatPoint = ref; - - - -// Point2D center = new Point2D(canvasWidth / 2, canvasHeight / 2); -// -// if (scaleDirection == ScaleDirection.HORIZONTAL) { -// referenceAngle = Math.abs( -// GeoUtility.getBearingRad(referencePoint, minLonPoint) -// ); -// referencePointX = -// bufferSize + distanceScaleFactor * Math.sin(referenceAngle) * GeoUtility -// .getDistance(referencePoint, minLonPoint); -// referenceAngle = Math.abs(GeoUtility.getDistance(referencePoint, maxLatPoint)); -// referencePointY = canvasHeight - (bufferSize + bufferSize); -// referencePointY -= distanceScaleFactor * Math.cos(referenceAngle) * GeoUtility -// .getDistance(referencePoint, maxLatPoint); -// referencePointY = referencePointY / 2; -// referencePointY += bufferSize; -// referencePointY += distanceScaleFactor * Math.cos(referenceAngle) * GeoUtility -// .getDistance(referencePoint, maxLatPoint); -// } else { -// referencePointY = canvasHeight - bufferSize; -// referenceAngle = Math.abs( -// Math.toRadians( -// GeoUtility.getDistance(referencePoint, minLonPoint) -// ) -// ); -// referencePointX = bufferSize; -// referencePointX += distanceScaleFactor * Math.sin(referenceAngle) * GeoUtility -// .getDistance(referencePoint, minLonPoint); -// referencePointX += -// ((canvasWidth - (bufferSize + bufferSize)) - (minLonToMaxLon * distanceScaleFactor)) -// / 2; -// } -// if (horizontalInversion) { -// referencePointX = canvasWidth - bufferSize - (referencePointX - bufferSize); -// } - } - - - /** - * Finds the scale factor necessary to fit all race markers within the onscreen map and assigns - * it to distanceScaleFactor Returns the max horizontal distance of the map. - */ - private double scaleRaceExtremities() { -// -// double vertAngle = Math.abs( -// GeoUtility.getBearingRad(minLatPoint, maxLatPoint) -// ); -// double vertDistance = -// Math.cos(vertAngle) * GeoUtility.getDistance(minLatPoint, maxLatPoint); -// double horiAngle = Math.abs( -// GeoUtility.getBearingRad(minLonPoint, maxLonPoint) -// ); -// if (horiAngle <= (Math.PI / 2)) { -// horiAngle = (Math.PI / 2) - horiAngle; -// } else { -// horiAngle = horiAngle - (Math.PI / 2); -// } -// double horiDistance = -// Math.cos(horiAngle) * GeoUtility.getDistance(minLonPoint, maxLonPoint); -// -// double vertScale = canvasHeight / vertDistance; -// -// if (horiDistance * vertScale > canvasWidth) { -// distanceScaleFactor = canvasWidth / horiDistance; -// scaleDirection = ScaleDirection.HORIZONTAL; -// } else { -// distanceScaleFactor = vertScale; -// scaleDirection = ScaleDirection.VERTICAL; -// } -// return horiDistance; - return 0; - } - - private Point2D findScaledXY(double unscaledLat, double unscaledLon) { - double distanceFromReference; - double angleFromReference; - double xAxisLocation = referencePointX; - double yAxisLocation = referencePointY; - - angleFromReference = GeoUtility.getBearingRad( - minLatPoint, new GeoPoint(unscaledLat, unscaledLon) - ); - distanceFromReference = GeoUtility.getDistance( - minLatPoint, new GeoPoint(unscaledLat, unscaledLon) - ); - if (angleFromReference >= 0 && angleFromReference <= Math.PI / 2) { - xAxisLocation += Math - .round(distanceScaleFactor * Math.sin(angleFromReference) * distanceFromReference); - yAxisLocation -= Math - .round(distanceScaleFactor * Math.cos(angleFromReference) * distanceFromReference); - } else if (angleFromReference >= 0) { - angleFromReference = angleFromReference - Math.PI / 2; - xAxisLocation += Math - .round(distanceScaleFactor * Math.cos(angleFromReference) * distanceFromReference); - yAxisLocation += Math - .round(distanceScaleFactor * Math.sin(angleFromReference) * distanceFromReference); - } else if (angleFromReference < 0 && angleFromReference >= -Math.PI / 2) { - angleFromReference = Math.abs(angleFromReference); - xAxisLocation -= Math - .round(distanceScaleFactor * Math.sin(angleFromReference) * distanceFromReference); - yAxisLocation -= Math - .round(distanceScaleFactor * Math.cos(angleFromReference) * distanceFromReference); - } else { - angleFromReference = Math.abs(angleFromReference) - Math.PI / 2; - xAxisLocation -= Math - .round(distanceScaleFactor * Math.cos(angleFromReference) * distanceFromReference); - yAxisLocation += Math - .round(distanceScaleFactor * Math.sin(angleFromReference) * distanceFromReference); - } - if (horizontalInversion) { - xAxisLocation = canvasWidth - bufferSize - (xAxisLocation - bufferSize); - } - return new Point2D(xAxisLocation, yAxisLocation); - } - - public void setHorizontalBuffer(Double buff){ - this.horizontalBuffer = buff; - } + public abstract Node getAssets(); + public abstract void updateCourse(List newCourse, List sequence); + public abstract void updateBorder(List border); } diff --git a/src/main/java/seng302/visualiser/GameView3D.java b/src/main/java/seng302/visualiser/GameView3D.java index 63d0203d..5be3b795 100644 --- a/src/main/java/seng302/visualiser/GameView3D.java +++ b/src/main/java/seng302/visualiser/GameView3D.java @@ -1,7 +1,6 @@ package seng302.visualiser; import java.util.ArrayList; -import java.util.Comparator; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -21,8 +20,8 @@ import javafx.scene.transform.Scale; import javafx.scene.transform.Translate; import seng302.gameServer.messages.RoundingSide; import seng302.model.ClientYacht; -import seng302.model.GeoPoint; import seng302.model.Limit; +import seng302.model.ScaledPoint; import seng302.model.mark.CompoundMark; import seng302.model.mark.Corner; import seng302.model.mark.Mark; @@ -30,7 +29,6 @@ import seng302.model.token.Token; import seng302.utilities.GeoUtility; import seng302.utilities.Sounds; import seng302.visualiser.fxObjects.MarkArrowFactory; -import seng302.visualiser.fxObjects.assets_3D.BoatMeshType; import seng302.visualiser.fxObjects.assets_3D.BoatObject; import seng302.visualiser.fxObjects.assets_3D.Marker3D; import seng302.visualiser.fxObjects.assets_3D.ModelFactory; @@ -40,57 +38,35 @@ import seng302.visualiser.fxObjects.assets_3D.ModelType; * Collection of animated3D assets that displays a race. */ -public class GameView3D { - +public class GameView3D extends GameView{ private final double FOV = 60; private final double DEFAULT_CAMERA_DEPTH = -125; private final double DEFAULT_CAMERA_X = 0; - private final double DEFAULT_CAMERA_Y = 155; + private final double DEFAULT_CAMERA_Y = 100; private Group root3D; private SubScene view; - // ParallelCamera camera; private PerspectiveCamera camera; - private Group gameObjects; - - private double bufferSize = 0; - private double canvasWidth = 200; - private double canvasHeight = 200; - private boolean horizontalInversion = false; - - private double distanceScaleFactor; - private ScaleDirection scaleDirection; - private GeoPoint minLatPoint, minLonPoint, maxLatPoint, maxLonPoint; - private double referencePointX, referencePointY; private Group raceBorder = new Group(); /* Note that if either of these is null then values for it have not been added and the other should be used as the limits of the map. */ - private List borderPoints; private Map markerObjects; - private Map boatObjects = new HashMap<>(); private BoatObject selectedBoat = null; private Group wakesGroup = new Group(); private Group boatObjectGroup = new Group(); - private Group markers = new Group(); - private Group tokens = new Group(); - private List course = new ArrayList<>(); private List mapTokens; private AnimationTimer playerBoatAnimationTimer; private Group trail = new Group(); private Double windDir; - private enum ScaleDirection { - HORIZONTAL, - VERTICAL - } - public GameView3D () { + canvasWidth = canvasHeight = 220; camera = new PerspectiveCamera(true); camera.getTransforms().addAll( - new Translate(DEFAULT_CAMERA_X,DEFAULT_CAMERA_Y, DEFAULT_CAMERA_DEPTH) + new Translate(DEFAULT_CAMERA_X, DEFAULT_CAMERA_Y, DEFAULT_CAMERA_DEPTH) ); camera.setFarClip(600); camera.setNearClip(0.1); @@ -114,8 +90,10 @@ public class GameView3D { }); } + @Override public void updateCourse(List newCourse, List sequence) { markerObjects = new HashMap<>(); + compoundMarks = newCourse; for (Corner corner : sequence) { //Makes course out of all compound marks. for (CompoundMark compoundMark : newCourse) { @@ -166,11 +144,13 @@ public class GameView3D { //Scale race to markers if there is no border. if (borderPoints == null) { - rescaleRace(new ArrayList<>(markerObjects.keySet())); + scaledPoint = ScaledPoint.makeScaledPoint( + canvasWidth, canvasHeight, new ArrayList<>(markerObjects.keySet()), true + ); } //Move the Markers to initial position. markerObjects.forEach(((mark, marker) -> { - Point2D p2d = findScaledXY(mark.getLat(), mark.getLng()); + Point2D p2d = scaledPoint.findScaledXY(mark.getLat(), mark.getLng()); marker.setLayoutX(p2d.getX()); marker.setLayoutY(p2d.getY()); })); @@ -190,7 +170,7 @@ public class GameView3D { private void makeAndBindMarker(Mark observableMark, ModelType markerType) { markerObjects.put(observableMark, new Marker3D(markerType)); observableMark.addPositionListener((mark, lat, lon) -> { - Point2D p2d = findScaledXY(lat, lon); + Point2D p2d = scaledPoint.findScaledXY(lat, lon); markerObjects.get(mark).setLayoutX(p2d.getX()); markerObjects.get(mark).setLayoutY(p2d.getY()); }); @@ -205,8 +185,8 @@ public class GameView3D { * @return the new gate. */ private Group makeGate(Mark m1, Mark m2, ModelType gateType) { - Point2D m1Location = findScaledXY(m1); - Point2D m2Location = findScaledXY(m2); + Point2D m1Location = scaledPoint.findScaledXY(m1); + Point2D m2Location = scaledPoint.findScaledXY(m2); Group barrier = ModelFactory.importModel(gateType).getAssets(); barrier.getTransforms().addAll( @@ -264,145 +244,6 @@ public class GameView3D { } } - /** - * Sets the class variables minLatPoint, maxLatPoint, minLonPoint, maxLonPoint to the point with - * the leftmost point, rightmost point, southern most point and northern most point - * respectively. - */ - private void findMinMaxPoint(List points) { - List sortedPoints = new ArrayList<>(points); - sortedPoints.sort(Comparator.comparingDouble(GeoPoint::getLat)); - minLatPoint = new GeoPoint(sortedPoints.get(0).getLat(), sortedPoints.get(0).getLng()); - GeoPoint maxLat = sortedPoints.get(sortedPoints.size() - 1); - maxLatPoint = new GeoPoint(maxLat.getLat(), maxLat.getLng()); - - sortedPoints.sort(Comparator.comparingDouble(GeoPoint::getLng)); - minLonPoint = new GeoPoint(sortedPoints.get(0).getLat(), sortedPoints.get(0).getLng()); - GeoPoint maxLon = sortedPoints.get(sortedPoints.size() - 1); - maxLonPoint = new GeoPoint(maxLon.getLat(), maxLon.getLng()); - if (maxLonPoint.getLng() - minLonPoint.getLng() > 180) { - horizontalInversion = true; - } - } - - /** - * Calculates the location of a reference point, this is always the point with minimum latitude, - * in relation to the canvas. - * - * @param minLonToMaxLon The horizontal distance between the point of minimum longitude to - * maximum longitude. - */ - private void calculateReferencePointLocation(double minLonToMaxLon) { - GeoPoint referencePoint = minLatPoint; - double referenceAngle; - - if (scaleDirection == ScaleDirection.HORIZONTAL) { - referenceAngle = Math.abs( - GeoUtility.getBearingRad(referencePoint, minLonPoint) - ); - referencePointX = - -100 + distanceScaleFactor * Math.sin(referenceAngle) * GeoUtility - .getDistance(referencePoint, minLonPoint); - referenceAngle = Math.abs(GeoUtility.getDistance(referencePoint, maxLatPoint)); - referencePointY = -100 + canvasHeight - (bufferSize + bufferSize); - referencePointY -= distanceScaleFactor * Math.cos(referenceAngle) * GeoUtility - .getDistance(referencePoint, maxLatPoint); - referencePointY = referencePointY / 2; - referencePointY += bufferSize; - referencePointY += distanceScaleFactor * Math.cos(referenceAngle) * GeoUtility - .getDistance(referencePoint, maxLatPoint); - } else { - referencePointY = -100 + canvasHeight - bufferSize; - referenceAngle = Math.abs( - Math.toRadians( - GeoUtility.getDistance(referencePoint, minLonPoint) - ) - ); - referencePointX = -100 + bufferSize; - referencePointX += distanceScaleFactor * Math.sin(referenceAngle) * GeoUtility - .getDistance(referencePoint, minLonPoint); - referencePointX += - ((canvasWidth - (bufferSize + bufferSize)) - (minLonToMaxLon * distanceScaleFactor)) - / 2; - } - if (horizontalInversion) { - referencePointX = -100 + canvasWidth - bufferSize - (referencePointX - bufferSize); - } - } - - - /** - * Finds the scale factor necessary to fit all race markers within the onscreen map and assigns - * it to distanceScaleFactor Returns the max horizontal distance of the map. - */ - private double scaleRaceExtremities() { - - double vertAngle = Math.abs( - GeoUtility.getBearingRad(minLatPoint, maxLatPoint) - ); - double vertDistance = - Math.cos(vertAngle) * GeoUtility.getDistance(minLatPoint, maxLatPoint); - double horiAngle = Math.abs( - GeoUtility.getBearingRad(minLonPoint, maxLonPoint) - ); - if (horiAngle <= (Math.PI / 2)) { - horiAngle = (Math.PI / 2) - horiAngle; - } else { - horiAngle = horiAngle - (Math.PI / 2); - } - double horiDistance = - Math.cos(horiAngle) * GeoUtility.getDistance(minLonPoint, maxLonPoint); - - double vertScale = (canvasHeight - (bufferSize + bufferSize)) / vertDistance; - - if ((horiDistance * vertScale) > (canvasWidth - (bufferSize + bufferSize))) { - distanceScaleFactor = (canvasWidth - (bufferSize + bufferSize)) / horiDistance; - scaleDirection = ScaleDirection.HORIZONTAL; - } else { - distanceScaleFactor = vertScale; - scaleDirection = ScaleDirection.VERTICAL; - } - return horiDistance; - } - - private Point2D findScaledXY(GeoPoint unscaled) { - return findScaledXY(unscaled.getLat(), unscaled.getLng()); - } - - private Point2D findScaledXY(double unscaledLat, double unscaledLon) { - double distanceFromReference; - double angleFromReference; - double xAxisLocation = referencePointX; - double yAxisLocation = referencePointY; - - angleFromReference = GeoUtility.getBearingRad( - minLatPoint, new GeoPoint(unscaledLat, unscaledLon) - ); - distanceFromReference = GeoUtility.getDistance( - minLatPoint, new GeoPoint(unscaledLat, unscaledLon) - ); - if (angleFromReference >= 0 && angleFromReference <= Math.PI / 2) { - xAxisLocation += distanceScaleFactor * Math.sin(angleFromReference) * distanceFromReference; - yAxisLocation -= distanceScaleFactor * Math.cos(angleFromReference) * distanceFromReference; - } else if (angleFromReference >= 0) { - angleFromReference = angleFromReference - Math.PI / 2; - xAxisLocation += distanceScaleFactor * Math.cos(angleFromReference) * distanceFromReference; - yAxisLocation += distanceScaleFactor * Math.sin(angleFromReference) * distanceFromReference; - } else if (angleFromReference < 0 && angleFromReference >= -Math.PI / 2) { - angleFromReference = Math.abs(angleFromReference); - xAxisLocation -= distanceScaleFactor * Math.sin(angleFromReference) * distanceFromReference; - yAxisLocation -= distanceScaleFactor * Math.cos(angleFromReference) * distanceFromReference; - } else { - angleFromReference = Math.abs(angleFromReference) - Math.PI / 2; - xAxisLocation -= distanceScaleFactor * Math.cos(angleFromReference) * distanceFromReference; - yAxisLocation += distanceScaleFactor * Math.sin(angleFromReference) * distanceFromReference; - } - if (horizontalInversion) { - xAxisLocation = canvasWidth - bufferSize - (xAxisLocation - bufferSize); - } - return new Point2D(xAxisLocation, yAxisLocation); - } - public void cameraMovement(KeyEvent event) { switch (event.getCode()) { case NUMPAD8: @@ -438,19 +279,6 @@ public class GameView3D { } } - /** - * Rescales the race to the size of the window. - * - * @param limitingCoordinates the set of geo points that contains the extremities of the race. - */ - private void rescaleRace(List limitingCoordinates) { - //Check is called once to avoid unnecessarily change the course limits once the race is running - findMinMaxPoint(limitingCoordinates); - double minLonToMaxLon = scaleRaceExtremities(); - calculateReferencePointLocation(minLonToMaxLon); -// drawGoogleMap(); - } - /** * Draws all the boats. * @param yachts The yachts to set in the race @@ -468,7 +296,7 @@ public class GameView3D { boatObjectGroup.getChildren().add(newBoat); clientYacht.addLocationListener((boat, lat, lon, heading, sailIn, velocity) -> { BoatObject bo = boatObjects.get(boat); - Point2D p2d = findScaledXY(lat, lon); + Point2D p2d = scaledPoint.findScaledXY(lat, lon); bo.moveTo(p2d.getX(), p2d.getY(), heading, velocity, sailIn, windDir); }); } @@ -491,18 +319,20 @@ public class GameView3D { public void updateBorder(List border) { if (borderPoints == null) { borderPoints = border; - rescaleRace(new ArrayList<>(borderPoints)); + scaledPoint = ScaledPoint.makeScaledPoint( + canvasWidth, canvasHeight, new ArrayList<>(borderPoints), true + ); } List boundaryAssets = new ArrayList<>(); - Point2D lastLocation = findScaledXY(border.get(0).getLat(), border.get(0).getLng()); + Point2D lastLocation = scaledPoint.findScaledXY(border.get(0).getLat(), border.get(0).getLng()); Group pylon = ModelFactory.importModel(ModelType.BORDER_PYLON).getAssets(); pylon.setLayoutX(lastLocation.getX()); pylon.setLayoutY(lastLocation.getY()); boundaryAssets.add(pylon); for (int i=1; i newTokens) { mapTokens = new ArrayList<>(); for (Token token : newTokens) { - Point2D location = findScaledXY(token.getLat(), token.getLng()); + Point2D location = scaledPoint.findScaledXY(token.getLat(), token.getLng()); Node tokenObject = ModelFactory.importModel(ModelType.VELOCITY_PICKUP).getAssets(); tokenObject.setLayoutX(location.getX()); tokenObject.setLayoutY(location.getY()); @@ -573,18 +403,17 @@ public class GameView3D { playerBoatAnimationTimer = new AnimationTimer() { double count = 60; - Point2D lastLocation = findScaledXY(playerYacht.getLocation()); + Point2D lastLocation = scaledPoint.findScaledXY(playerYacht.getLocation()); @Override public void handle(long now) { if (--count == 0) { count = 60; Node segment = ModelFactory.importModel(ModelType.TRAIL_SEGMENT).getAssets(); - Point2D location = findScaledXY(playerYacht.getLocation()); + Point2D location = scaledPoint.findScaledXY(playerYacht.getLocation()); segment.getTransforms().addAll( new Translate(location.getX(), location.getY(), 0), - new Rotate(playerYacht.getHeading(), new Point3D(0,0,1)), - new Scale(1, lastLocation.distance(location) / 5, 1) + new Rotate(playerYacht.getHeading(), new Point3D(0,0,1)) ); trail.getChildren().add(segment); if (trail.getChildren().size() > 50) { diff --git a/src/main/java/seng302/visualiser/MapMaker.java b/src/main/java/seng302/visualiser/MapMaker.java index 954f392c..be70293e 100644 --- a/src/main/java/seng302/visualiser/MapMaker.java +++ b/src/main/java/seng302/visualiser/MapMaker.java @@ -5,6 +5,7 @@ import java.io.IOException; import java.io.StringReader; import java.util.ArrayList; import java.util.List; +import javafx.scene.Node; import javafx.util.Pair; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; @@ -21,7 +22,7 @@ import seng302.utilities.XMLParser; */ public class MapMaker { - private List gameViews = new ArrayList<>(); + private List mapPreviews = new ArrayList<>(); private List races = new ArrayList<>(); private List regattas = new ArrayList<>(); private List filePaths = new ArrayList<>(); @@ -61,11 +62,11 @@ public class MapMaker { e.printStackTrace(); } RaceXMLData race = XMLParser.parseRace(doc); - GameView gameView = new GameView( + MapPreview mapPreview = new MapPreview( new ArrayList<>(race.getCompoundMarks().values()), race.getMarkSequence(), race.getCourseLimit() ); - gameViews.add(gameView); + mapPreviews.add(mapPreview); races.add(race); } } @@ -73,7 +74,7 @@ public class MapMaker { public void next() { index += 1; - if (index >= gameViews.size()) { + if (index >= mapPreviews.size()) { index = 0; } } @@ -81,12 +82,12 @@ public class MapMaker { public void previous() { index -= 1; if (index < 0) { - index = gameViews.size() - 1; + index = mapPreviews.size() - 1; } } - public GameView getCurrentGameView() { - return gameViews.get(index); + public Node getCurrentGameView() { + return mapPreviews.get(index).getAssets(); } public RaceXMLData getCurrentRace() { diff --git a/src/main/java/seng302/visualiser/MapPreview.java b/src/main/java/seng302/visualiser/MapPreview.java new file mode 100644 index 00000000..a733967a --- /dev/null +++ b/src/main/java/seng302/visualiser/MapPreview.java @@ -0,0 +1,283 @@ +package seng302.visualiser; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import javafx.application.Platform; +import javafx.geometry.Point2D; +import javafx.scene.Node; +import javafx.scene.paint.Color; +import javafx.scene.paint.Paint; +import javafx.scene.shape.Polygon; +import seng302.gameServer.messages.RoundingSide; +import seng302.model.GeoPoint; +import seng302.model.Limit; +import seng302.model.ScaledPoint; +import seng302.model.mark.CompoundMark; +import seng302.model.mark.Corner; +import seng302.model.mark.Mark; +import seng302.utilities.GeoUtility; +import seng302.visualiser.fxObjects.MarkArrowFactory; +import seng302.visualiser.fxObjects.assets_2D.CourseBoundary; +import seng302.visualiser.fxObjects.assets_2D.Gate; +import seng302.visualiser.fxObjects.assets_2D.Marker2D; + +/** + * Created by cir27 on 20/07/17. + */ +public class MapPreview extends GameView { + + private Polygon raceBorder = new CourseBoundary(); + private Map markerObjects; + + public MapPreview(List marks, List course, List border) { + this.compoundMarks = marks; + this.courseOrder = course; + this.borderPoints = border; + gameObjects.getChildren().addAll(raceBorder, markers, tokens); + gameObjects.parentProperty().addListener((obs, old, parent) -> { + if (parent != null) { + canvasWidth = parent.prefWidth(1); + canvasHeight = parent.prefHeight(1); + updateBorder(borderPoints); + updateCourse(compoundMarks, courseOrder); + } + }); + } + + @Override + public Node getAssets() { + return gameObjects; + } + + public void setSize(double width, double height) { + canvasHeight = height; + canvasWidth = width; + updateBorder(borderPoints); + updateCourse(compoundMarks, courseOrder); + } + + /** + * Adds a course to the GameView. The view is scaled accordingly unless a border is set in which + * case the course is added relative ot the border. + * + * @param newCourse the mark objects that make up the course. + * @param sequence The sequence the marks travel through + */ + @Override + public void updateCourse(List newCourse, List sequence) { + + if (newCourse.size() == 0) { + return; + } + compoundMarks = newCourse; + markerObjects = new HashMap<>(); + courseOrder = sequence; + + for (Corner corner : courseOrder) { //Makes course out of all compound marks. + for (CompoundMark compoundMark : newCourse) { + if (corner.getCompoundMarkID() == compoundMark.getId()) { + course.add(compoundMark); + } + } + } + + // TODO: 16/08/17 Updating mark roundings here. It should not happen here. Nor should it be done this way. + for (Corner corner : sequence){ + CompoundMark compoundMark = course.get(corner.getSeqID() - 1); + compoundMark.setRoundingSide( + RoundingSide.getRoundingSide(corner.getRounding()) + ); + } + + final List gates = new ArrayList<>(); + Paint colour = Color.BLACK; + //Creates new markers + for (CompoundMark cMark : newCourse) { + //Set start and end colour + if (cMark.getId() == sequence.get(0).getCompoundMarkID()) { + colour = Color.GREEN; + } else if (cMark.getId() == sequence.get(sequence.size() - 1).getCompoundMarkID()) { + colour = Color.RED; + } + //Create mark dots + for (Mark mark : cMark.getMarks()) { + makeAndBindMarker(mark, colour); + } + //Create gate line + if (cMark.isGate()) { + for (int i = 1; i < cMark.getMarks().size(); i++) { + gates.add( + makeAndBindGate( + markerObjects.get(cMark.getSubMark(i)), + markerObjects.get(cMark.getSubMark(i + 1)), + colour + ) + ); + } + } + colour = Color.BLACK; + } + + createMarkArrows(sequence); + + //Scale race to markers if there is no border. + if (borderPoints == null) { + scaledPoint = ScaledPoint.makeScaledPoint( + canvasWidth, canvasHeight, new ArrayList<>(markerObjects.keySet()), false + ); + } + //Move the Markers to initial position. + markerObjects.forEach(((mark, marker2D) -> { + Point2D p2d = scaledPoint.findScaledXY(mark.getLat(), mark.getLng()); + marker2D.setLayoutX(p2d.getX()); + marker2D.setLayoutY(p2d.getY()); + })); + Platform.runLater(() -> { + markers.getChildren().clear(); + markers.getChildren().addAll(gates); + markers.getChildren().addAll(markerObjects.values()); + }); + } + + /** + * Calculates all the data needed for to create mark arrows. Requires that a course has been + * added to the gameview. + * @param sequence The order in which marks are traversed. + */ + private void createMarkArrows (List sequence) { + for (int i=1; i < sequence.size()-1; i++) { //General case. + double averageLat = 0; + double averageLng = 0; + int numMarks = course.get(i-1).getMarks().size(); + for (Mark mark : course.get(i-1).getMarks()) { + averageLat += mark.getLat(); + averageLng += mark.getLng(); + } + GeoPoint lastMarkAv = new GeoPoint(averageLat / numMarks, averageLng / numMarks); + numMarks = course.get(i+1).getMarks().size(); + averageLat = 0; + averageLng = 0; + for (Mark mark : course.get(i+1).getMarks()) { + averageLat += mark.getLat(); + averageLng += mark.getLng(); + } + GeoPoint nextMarkAv = new GeoPoint(averageLat / numMarks, averageLng / numMarks); + // TODO: 16/08/17 This comparison doesn't need to exist but the alternative is to user server enum client side. + for (Mark mark : course.get(i).getMarks()) { + markerObjects.get(mark).addArrows( + mark.getRoundingSide() == RoundingSide.STARBOARD ? MarkArrowFactory.RoundingSide.STARBOARD : MarkArrowFactory.RoundingSide.PORT, + GeoUtility.getBearing(lastMarkAv, mark), + GeoUtility.getBearing(mark, nextMarkAv) + ); + } + } + createStartLineArrows(); + createFinishLineArrows(); + } + + private void createStartLineArrows () { + double averageLat = 0; + double averageLng = 0; + int numMarks = 0; + for (Mark mark : course.get(1).getMarks()) { + numMarks += 1; + averageLat += mark.getLat(); + averageLng += mark.getLng(); + } + GeoPoint firstMarkAv = new GeoPoint(averageLat / numMarks, averageLng / numMarks); + for (Mark mark : course.get(0).getMarks()) { + markerObjects.get(mark).addArrows( + mark.getRoundingSide() == RoundingSide.STARBOARD ? MarkArrowFactory.RoundingSide.STARBOARD : MarkArrowFactory.RoundingSide.PORT, + 0d, //90 + GeoUtility.getBearing(mark, firstMarkAv) + ); + } + } + + private void createFinishLineArrows () { + double numMarks = 0; + double averageLat = 0; + double averageLng = 0; + for (Mark mark : course.get(course.size()-2).getMarks()) { + numMarks += 1; + averageLat += mark.getLat(); + averageLng += mark.getLng(); + } + GeoPoint secondToLastMarkAv = new GeoPoint(averageLat / numMarks, averageLng / numMarks); + for (Mark mark : course.get(course.size()-1).getMarks()) { + markerObjects.get(mark).addArrows( + mark.getRoundingSide() == RoundingSide.STARBOARD ? MarkArrowFactory.RoundingSide.STARBOARD : MarkArrowFactory.RoundingSide.PORT, + GeoUtility.getBearing(secondToLastMarkAv, mark), + GeoUtility.getBearing(mark, mark) + ); + } + } + + /** + * Creates a new Marker and binds it's position to the given Mark. + * + * @param observableMark The mark to bind the marker to. + * @param colour The desired colour of the mark + */ + private void makeAndBindMarker(Mark observableMark, Paint colour) { + Marker2D marker2D = new Marker2D(colour); +// marker.addArrows(MarkArrowFactory.RoundingSide.PORT, ThreadLocalRandom.current().nextDouble(91, 180), ThreadLocalRandom.current().nextDouble(1, 90)); + markerObjects.put(observableMark, marker2D); + observableMark.addPositionListener((mark, lat, lon) -> { + Point2D p2d = scaledPoint.findScaledXY(lat, lon); + markerObjects.get(mark).setLayoutX(p2d.getX()); + markerObjects.get(mark).setLayoutY(p2d.getY()); + }); + } + + /** + * Creates a new gate connecting the given marks. + * + * @param m1 The first Mark of the gate. + * @param m2 The second Mark of the gate. + * @param colour The desired colour of the gate. + * @return the new gate. + */ + private Gate makeAndBindGate(Marker2D m1, Marker2D m2, Paint colour) { + Gate gate = new Gate(colour); + gate.startXProperty().bind( + m1.layoutXProperty() + ); + gate.startYProperty().bind( + m1.layoutYProperty() + ); + gate.endXProperty().bind( + m2.layoutXProperty() + ); + gate.endYProperty().bind( + m2.layoutYProperty() + ); + return gate; + } + + /** + * Adds a border to the GameView and rescales to the size of the border, does not rescale if a + * border already exists. Assumes the border is larger than the course. + * + * @param border the race border to be drawn. + */ + @Override + public void updateBorder(List border) { + if (border.size() == 0) { + return; + } + + borderPoints = border; + scaledPoint = ScaledPoint.makeScaledPoint(canvasWidth, canvasHeight, border, false); + + List boundaryPoints = new ArrayList<>(); + for (Limit limit : border) { + Point2D location = scaledPoint.findScaledXY(limit.getLat(), limit.getLng()); + boundaryPoints.add(location.getX()); + boundaryPoints.add(location.getY()); + } + raceBorder.getPoints().setAll(boundaryPoints); + } +} diff --git a/src/main/java/seng302/visualiser/controllers/LobbyController.java b/src/main/java/seng302/visualiser/controllers/LobbyController.java index d0280736..bd0eb7c6 100644 --- a/src/main/java/seng302/visualiser/controllers/LobbyController.java +++ b/src/main/java/seng302/visualiser/controllers/LobbyController.java @@ -29,7 +29,7 @@ import seng302.model.mark.CompoundMark; import seng302.model.mark.Corner; import seng302.model.stream.xml.parser.RaceXMLData; import seng302.utilities.Sounds; -import seng302.visualiser.GameView; +import seng302.visualiser.MapPreview; import seng302.visualiser.controllers.cells.PlayerCell; import seng302.visualiser.controllers.dialogs.BoatCustomizeController; @@ -60,7 +60,7 @@ public class LobbyController implements Initializable { public Color playersColor; private Map playerBoats; private Double mapWidth = INITIAL_MAP_WIDTH, mapHeight = INITIAL_MAP_HEIGHT; - private GameView gameView; + private MapPreview mapPreview; @Override public void initialize(URL location, ResourceBundle resources) { @@ -146,20 +146,20 @@ public class LobbyController implements Initializable { List marks = new ArrayList<>(raceData.getCompoundMarks().values()); List corners = raceData.getMarkSequence(); - gameView = new GameView(marks, corners, border); + mapPreview = new MapPreview(marks, corners, border); serverMap.getChildren().clear(); - serverMap.getChildren().add(gameView); + serverMap.getChildren().add(mapPreview.getAssets()); - gameView.setSize(mapWidth, mapHeight); + mapPreview.setSize(mapWidth, mapHeight); serverMap.widthProperty().addListener((observable, oldValue, newValue) -> { mapWidth = newValue.doubleValue(); - gameView.setSize(mapWidth, mapHeight); + mapPreview.setSize(mapWidth, mapHeight); }); // serverMap.heightProperty().addListener((observable, oldValue, newValue) -> { mapHeight = newValue.doubleValue(); - gameView.setSize(mapWidth, mapHeight); + mapPreview.setSize(mapWidth, mapHeight); }); } diff --git a/src/main/java/seng302/visualiser/controllers/RaceViewController.java b/src/main/java/seng302/visualiser/controllers/RaceViewController.java index 3d011a0d..2838a4d2 100644 --- a/src/main/java/seng302/visualiser/controllers/RaceViewController.java +++ b/src/main/java/seng302/visualiser/controllers/RaceViewController.java @@ -12,8 +12,6 @@ import java.util.concurrent.TimeUnit; import javafx.animation.Timeline; import javafx.application.Platform; import javafx.beans.property.ReadOnlyBooleanProperty; -import javafx.beans.value.ChangeListener; -import javafx.beans.value.ObservableValue; import javafx.collections.FXCollections; import javafx.collections.ListChangeListener; import javafx.collections.ObservableList; @@ -24,8 +22,6 @@ import javafx.scene.Scene; import javafx.scene.SubScene; import javafx.scene.chart.LineChart; import javafx.scene.chart.NumberAxis; -import javafx.scene.chart.XYChart; -import javafx.scene.chart.XYChart.Data; import javafx.scene.chart.XYChart.Series; import javafx.scene.control.Button; import javafx.scene.control.CheckBox; @@ -134,32 +130,6 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel Sounds.stopMusic(); Sounds.playRaceMusic(); - // Load a default important annotation state - //importantAnnotations = new ImportantAnnotationsState(); - - //Formatting the y axis of the sparkline -// raceSparkLine.getYAxis().setRotate(180); -// raceSparkLine.getYAxis().setTickLabelRotation(180); -// raceSparkLine.getYAxis().setTranslateX(-5); - //raceSparkLine.visibleProperty().setValue(false); - //raceSparkLine.getYAxis().setAutoRanging(false); - //sparklineYAxis.setTickMarkVisible(false); - - //positionVbox.getStylesheets().add(getClass().getResource("/css/master.css").toString()); -// raceSparkLine.visibleProperty().setValue(false); -// raceSparkLine.getYAxis().setAutoRanging(false); -// sparklineYAxis.setTickMarkVisible(false); -// positionVbox.getStylesheets().add(getClass().getResource("/css/master.css").toString()); - - //selectAnnotationBtn.setOnAction(event -> loadSelectAnnotationView()); -// rvAnchorPane.prefWidthProperty().bind(ViewManager.getInstance().getDecorator().widthProperty()); -// rvAnchorPane.prefHeightProperty().bind(ViewManager.getInstance().getDecorator().heightProperty()); -// selectAnnotationBtn.setOnAction(event -> loadSelectAnnotationView()); -// windArrowHolder.getChildren().addAll(windArrow); -// windArrow.setLayoutX(windArrowHolder.getWidth() / 2); -// windArrow.setLayoutY(windArrowHolder.getHeight() / 2); - -// selectAnnotationBtn.setOnAction(event -> loadSelectAnnotationView()); chatInput.lengthProperty().addListener((obs, oldLen, newLen) -> { if (newLen.intValue() > CHAT_LIMIT) { chatInput.setText(chatInput.getText().substring(0, CHAT_LIMIT)); @@ -173,26 +143,17 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel chatHistory.prefHeightProperty().bind( chatHistoryHolder.heightProperty() ); -// chatHistory.setFitToWidth(true); -// chatHistory.setFitToHeight(true); -// chatHistory.textProperty().addListener((obs, oldValue, newValue) -> { -// chatHistory.setScrollTop(Double.MAX_VALUE); -// }); contentStackPane.setOnMouseClicked(event -> { contentStackPane.requestFocus(); }); - + Platform.runLater(contentStackPane::requestFocus); //Makes the chat history non transparent when clicked on - chatInput.focusedProperty().addListener(new ChangeListener() { - @Override - public void changed(ObservableValue observable, Boolean oldValue, - Boolean newValue) { - if (newValue) { - chatHistory.increaseOpacity(); - } else { - chatHistory.decreaseOpacity(); - } + chatInput.focusedProperty().addListener((observable, oldValue, newValue) -> { + if (newValue) { + chatHistory.increaseOpacity(); + } else { + chatHistory.decreaseOpacity(); } }); } @@ -236,14 +197,12 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel while (c.next()) { if (c.wasPermutated()) { updateOrder(raceState.getPlayerPositions()); - updateSparkLine(); } } }); updateOrder(raceState.getPlayerPositions()); gameView = new GameView3D(); -// gameView.setFrameRateFXText(fpsDisplay); Platform.runLater(() -> { contentStackPane.getChildren().add(0, gameView.getAssets()); ((SubScene) gameView.getAssets()).widthProperty() @@ -257,11 +216,10 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel gameView.updateCourse( new ArrayList<>(raceData.getCompoundMarks().values()), raceData.getMarkSequence() ); -// gameView.enableZoom(); gameView.setBoatAsPlayer(player); -// gameView.startRace(); // raceState.addCollisionListener(gameView::drawCollision); + raceState.windDirectionProperty().addListener((obs, oldDirection, newDirection) -> { gameView.setWindDir(newDirection.doubleValue()); Platform.runLater(() -> updateWindDirection(newDirection.doubleValue())); @@ -274,9 +232,7 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel updateWindSpeed(raceState.getWindSpeed()); }); gameView.setWindDir(raceState.windDirectionProperty().doubleValue()); - Platform.runLater(() -> { - initializeUpdateTimer(); - }); + Platform.runLater(this::initializeUpdateTimer); } /** @@ -317,137 +273,6 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel } } - private void initialiseFPSCheckBox() { -// toggleFps.selectedProperty().addListener((obs, oldVal, newVal) -> -// gameView.setFPSVisibility(toggleFps.isSelected()) -// ); - } - - private void initialiseAnnotationSlider() { -// annotationSlider.setLabelFormatter(new StringConverter() { -// @Override -// public String toString(Double n) { -// if (n == 0) { -// return "None"; -// } -// if (n == 1) { -// return "Important"; -// } -// if (n == 2) { -// return "All"; -// } -// return "All"; -// } -// -// @Override -// public Double fromString(String s) { -// switch (s) { -// case "None": -// return 0d; -// case "Important": -// return 1d; -// case "All": -// return 2d; -// -// default: -// return 2d; -// } -// } -// }); -// annotationSlider.setValue(2); -// annotationSlider.valueProperty().addListener((obs, oldVal, newVal) -> -// setAnnotations((int) annotationSlider.getValue()) -// ); - } - - - /** - * Used to add any new yachts into the race that may have started late or not have had data received yet - */ - private void updateSparkLine(){ -// // TODO: 2/08/17 there is about 0 chance of this working. Once we are keeping track of boat positions it can be fixed. -// // Collect the racing yachts that aren't already in the chart -// sparkLineData.clear(); -// List sparkLineCandidates = new ArrayList<>(participants.values()); -// // Create a new data series for new yachts -// sparkLineCandidates -// .stream() -// .filter(yacht -> yacht.getPosition() != null) -// .forEach(yacht -> { -// Series yachtData = new Series<>(); -// yachtData.setName(yacht.getSourceId().toString()); -// yachtData.getData().add( -// new Data<>( -// Integer.toString(yacht.getLegNumber()), -// 1.0 + participants.size() - yacht.getPosition() -// ) -// ); -// sparkLineData.add(yachtData); -// }); -// -// // Lambda function to sort the series in order of leg (later legs shown more to the right) -// sparkLineData.sort((o1, o2) -> { -// Integer leg1 = Integer.parseInt(o1.getData().get(o1.getData().size()-1).getXValue()); -// Integer leg2 = Integer.parseInt(o2.getData().get(o2.getData().size()-1).getXValue()); -// if (leg2 < leg1){ -// return 1; -// } else { -// return -1; -// } -// }); -// -// // Adds the new data series to the sparkline (and set the colour of the series) -// Platform.runLater(() -> { -// sparkLineData -// .stream() -// .filter(spark -> !raceSparkLine.getData().contains(spark)) -// .forEach(spark -> { -// raceSparkLine.getData().add(spark); -// spark.getNode().lookup(".chart-series-line").setStyle("-fx-stroke:" + getBoatColorAsRGB(spark.getName())); -// }); -// }); - } - - private void initialiseSparkLine() { -// sparklineYAxis.setUpperBound(participants.size() + 1); -// raceSparkLine.setCreateSymbols(false); - } - - /** - * Updates the yachts sparkline of the desired yacht and using the new leg number - * @param yacht The yacht to be updated on the sparkline - * @param legNumber the leg number that the position will be assigned to - */ - void updateYachtPositionSparkline(ClientYacht yacht, Integer legNumber){ - for (XYChart.Series positionData : sparkLineData) { - positionData.getData().add( - new Data<>( - Integer.toString(legNumber), - 1.0 + participants.size() - yacht.getPlacing() - ) - ); - } - } - - - /** - * gets the rgb string of the yachts colour to use for the chart via css - * @param yachtId id of yacht passed in to get the yachts colour - * @return the colour as an rgb string - */ - private String getBoatColorAsRGB(String yachtId){ - Color color = participants.get(Integer.valueOf(yachtId)).getColour(); - if (color == null){ - return String.format("#%02X%02X%02X",255,255,255); - } - return String.format( "#%02X%02X%02X", - (int)( color.getRed() * 255 ), - (int)( color.getGreen() * 255 ), - (int)( color.getBlue() * 255 ) - ); - } - - /** * Initialises a timer which updates elements of the RaceView such as wind direction, yacht * orderings etc.. which are dependent on the info from the stream parser constantly. diff --git a/src/main/resources/maps/horseshoe.xml b/src/main/resources/maps/horseshoe.xml index 642f1deb..b917eb32 100644 --- a/src/main/resources/maps/horseshoe.xml +++ b/src/main/resources/maps/horseshoe.xml @@ -8,32 +8,32 @@ - - + + - + - + - - + + - - + + - + - + - - + + @@ -60,18 +60,18 @@ - - - - - - - - - - - - + + + + + + + + + + + + \ No newline at end of file