package seng302.Model; import javafx.scene.canvas.Canvas; import javafx.scene.canvas.GraphicsContext; import javafx.scene.paint.Color; import javafx.scene.paint.Paint; import javafx.scene.transform.Rotate; import seng302.*; import java.util.ArrayList; import java.util.List; /** * This creates a JavaFX Canvas that is fills it's parent. * Cannot be downsized. * Created by fwy13 on 17/03/17. */ public class ResizableRaceCanvas extends Canvas { double[] xpoints = {}, ypoints = {}; private GraphicsContext gc; private RaceMap map; private List boats; private boolean raceAnno = true; private boolean annoName = true; private boolean annoAbbrev = true; private boolean annoSpeed = true; private boolean annoPath = true; private ArrayList raceBoundaries; public ResizableRaceCanvas(RaceDataSource raceData) { gc = this.getGraphicsContext2D(); // Redraw canvas when size changes. widthProperty().addListener(evt -> drawRaceMap()); heightProperty().addListener(evt -> drawRaceMap()); double lat1 = raceData.getMapTopLeft().getLatitude(); double long1 = raceData.getMapTopLeft().getLongitude(); double lat2 = raceData.getMapBottomRight().getLatitude(); double long2 = raceData.getMapBottomRight().getLongitude(); setMap(new RaceMap(lat1, long1, lat2, long2, (int) getWidth(), (int) getHeight())); } /** * Sets the boats that are to be displayed in this race. * * @param boats in race */ public void setBoats(List boats) { this.boats = boats; } /** * Sets the RaceMap that the RaceCanvas is to be displaying for. * * @param map race map */ public void setMap(RaceMap map) { this.map = map; } /** * Displays the mark of a race as a circle. * * @param graphCoordinate Latitude and Logintude in GraphCoordinate that it is to be displayed as. * @param paint Colour the mark is to be coloured. * @see GraphCoordinate * @see Color * @see Paint */ public void displayMark(GraphCoordinate graphCoordinate, Paint paint) { double d = 25; gc.setFill(paint); gc.fillOval(graphCoordinate.getX() - (d / 2), graphCoordinate.getY() - (d / 2), d, d); } public void displayBoat(BoatInRace boat, double angle) { GraphCoordinate pos = this.map.convertGPS(boat.getCurrentPosition()); Paint paint = boat.getColour(); double[] x = {pos.getX() - 6, pos.getX(), pos.getX() + 6}; double[] y = {pos.getY() + 12, pos.getY() - 12, pos.getY() + 12}; gc.setFill(paint); gc.save(); rotate(angle, pos.getX(), pos.getY()); gc.fillPolygon(x, y, 3); gc.restore(); } /** * Displays a line on the map with rectangles on the starting and ending point of the line. * * @param graphCoordinateA Starting Point of the line in GraphCoordinate. * @param graphCoordinateB End Point of the line in GraphCoordinate. * @param paint Colour the line is to coloured. * @see GraphCoordinate * @see Color * @see Paint */ private void displayLine(GraphCoordinate graphCoordinateA, GraphCoordinate graphCoordinateB, Paint paint) { gc.setStroke(paint); gc.setFill(paint); gc.fillOval(graphCoordinateA.getX() - 3, graphCoordinateA.getY() - 3, 6, 6); gc.fillOval(graphCoordinateB.getX() - 3, graphCoordinateB.getY() - 3, 6, 6); gc.strokeLine(graphCoordinateA.getX(), graphCoordinateA.getY(), graphCoordinateB.getX(), graphCoordinateB.getY()); } /** * Display a point on the Canvas * * @param graphCoordinate Coordinate that the point is to be displayed at. * @param paint Colour that the boat is to be coloured. * @see GraphCoordinate * @see Paint * @see Color */ private void displayPoint(GraphCoordinate graphCoordinate, Paint paint) { gc.setFill(paint); gc.fillOval(graphCoordinate.getX(), graphCoordinate.getY(), 10, 10); } /** * Displays an arrow on the Canvas * * @param coordinate Coordinate that the arrow is to be displayed at. * @param angle Angle that the arrow is to be facing in degrees 0 degrees = North (Up). * @see GraphCoordinate */ private void displayArrow(GraphCoordinate coordinate, int angle) { gc.save(); rotate(angle, coordinate.getX(), coordinate.getY()); gc.setFill(Color.BLACK); gc.fillPolygon(new double[]{coordinate.getX() - 12, coordinate.getX() - 6, coordinate.getX(), coordinate.getX() - 4, coordinate.getX() - 4, coordinate.getX() - 8, coordinate.getX() - 8}, new double[]{coordinate.getY() - 5, coordinate.getY() - 20, coordinate.getY() - 5, coordinate.getY() - 5, coordinate.getY() + 20, coordinate.getY() + 20, coordinate.getY() - 5}, 7); gc.restore(); } /** * Rotates things on the canvas Note: this must be called in between gc.save() and gc.restore() else they will rotate everything * * @param angle Bearing angle to rotate at in degrees * @param px Pivot point x of rotation. * @param py Pivot point y of rotation. */ private void rotate(double angle, double px, double py) { Rotate r = new Rotate(angle, px, py); gc.setTransform(r.getMxx(), r.getMyx(), r.getMxy(), r.getMyy(), r.getTx(), r.getTy()); } /** * Display given name and speed of boat at a graph coordinate * * @param name name of the boat * @param abbrev abbreviation of the boat name * @param speed speed of the boat * @param coordinate coordinate the text appears */ private void displayText(String name, String abbrev, double speed, GraphCoordinate coordinate) { String text = ""; //Check name toggle value if (annoName) { text += String.format("%s ", name); } //Check abbreviation toggle value if (annoAbbrev) { text += String.format("%s ", abbrev); } //Check speed toggle value if (annoSpeed) { text += String.format("%.2fkn", speed); } //String text = String.format("%s, %2$.2fkn", name, speed); long xCoord = coordinate.getX() + 20; long yCoord = coordinate.getY(); if (xCoord + (text.length() * 7) >= getWidth()) { xCoord -= text.length() * 7; } if (yCoord - (text.length() * 2) <= 0) { yCoord += 30; } gc.fillText(text, xCoord, yCoord); } /** * Draws race map with up to date data. */ public void update() { this.drawRaceMap(); this.updateBoats(); } /** * Draw boundary of the race. */ public void drawBoundaries() { if (this.raceBoundaries == null) { return; } gc.setFill(Color.AQUA); setRaceBoundCoordinates(); gc.fillPolygon(xpoints, ypoints, xpoints.length); } /** * Draws the Race Map */ public void drawRaceMap() { double width = getWidth(); double height = getHeight(); gc.clearRect(0, 0, width, height); if (map == null) { return;//TODO this should return a exception in the future } this.map.setHeight((int) height); this.map.setWidth((int) width); //finish line gc.setLineWidth(2); drawBoundaries(); GraphCoordinate finishLineCoord1 = this.map.convertGPS(Constants.finishLineMarker1); GraphCoordinate finishLineCoord2 = this.map.convertGPS(Constants.finishLineMarker2); displayLine(finishLineCoord1, finishLineCoord2, Color.DARKRED); //marks GraphCoordinate markCoord = this.map.convertGPS(Constants.mark1); GraphCoordinate windwardGate1 = this.map.convertGPS(Constants.windwardGate1); GraphCoordinate windwardGate2 = this.map.convertGPS(Constants.windwardGate2); GraphCoordinate leewardGate1 = this.map.convertGPS(Constants.leewardGate1); GraphCoordinate leewardGate2 = this.map.convertGPS(Constants.leewardGate2); displayMark(markCoord, Color.GOLD); displayLine(windwardGate1, windwardGate2, Color.DARKCYAN); displayLine(leewardGate1, leewardGate2, Color.DARKVIOLET); //start line GraphCoordinate startline1 = this.map.convertGPS(Constants.startLineMarker1); GraphCoordinate startline2 = this.map.convertGPS(Constants.startLineMarker2); displayLine(startline1, startline2, Color.GREEN); updateBoats(); //display wind direction arrow - specify origin point and angle - angle now set to random angle displayArrow(new GraphCoordinate((int) getWidth() - 40, 40), 150); } /** * Draws a boat at a certain GPSCoordinate * * @param colour Colour to colour boat. * @param gpsCoordinates GPScoordinate that the boat is to be drawn at. * @see GPSCoordinate * @see Color */ public void drawBoat(Color colour, GPSCoordinate gpsCoordinates) { GraphCoordinate graphCoordinate = this.map.convertGPS(gpsCoordinates); displayPoint(graphCoordinate, colour); } /** * Toggle the raceAnno value */ public void toggleAnnotations() { if (raceAnno) { raceAnno = false; } else { raceAnno = true; } } /** * Toggle name display in annotation */ public void toggleAnnoName() { if (annoName) { annoName = false; } else { annoName = true; } } public void toggleBoatPath() { if (annoPath) { annoPath = false; } else { annoPath = true; } } /** * Toggle abbreviation display in annotation */ public void toggleAnnoAbbrev() { if (annoAbbrev) { annoAbbrev = false; } else { annoAbbrev = true; } } /** * Toggle speed display in annotation */ public void toggleAnnoSpeed() { if (annoSpeed) { annoSpeed = false; } else { annoSpeed = true; } } /** * Draws boats while race in progress, when leg heading is set. */ public void updateBoats() { // TODO Remove null when boats are ready boats = null; if (boats != null) { for (BoatInRace boat : boats) { boolean finished = boat.getCurrentLeg().getName().equals("Finish") || boat.getCurrentLeg().getName().equals("DNF"); boolean isStart = boat.isStarted(); if (!finished && isStart) { displayBoat(boat, boat.getHeading()); GraphCoordinate wakeFrom = this.map.convertGPS(boat.getCurrentPosition()); GraphCoordinate wakeTo = this.map.convertGPS(boat.getWake()); displayLine(wakeFrom, wakeTo, boat.getColour()); } else if (!isStart) { displayBoat(boat, boat.getHeading()); } else { displayBoat(boat, 0); } if (raceAnno) displayText(boat.toString(), boat.getAbbrev(), boat.getVelocity(), this.map.convertGPS(boat.getCurrentPosition())); if (boat.isTrackVisible()) drawTrack(boat); } } } /** * Draws all track points for a given boat. Colour is set by boat, opacity by track point. * * @param boat whose track is displayed * @see seng302.Model.TrackPoint */ private void drawTrack(BoatInRace boat) { if (annoPath && raceAnno) { for (TrackPoint point : boat.getTrack()) { GraphCoordinate scaledCoordinate = this.map.convertGPS(point.getCoordinate()); Color boatColour = boat.getColour(); gc.setFill(new Color(boatColour.getRed(), boatColour.getGreen(), boatColour.getBlue(), point.getAlpha())); gc.fillOval(scaledCoordinate.getX(), scaledCoordinate.getY(), 5, 5); } } } public void setRaceBoundaries(List boundaries) { this.raceBoundaries = new ArrayList<>(); for (GPSCoordinate bound : boundaries) { raceBoundaries.add(bound); } setRaceBoundCoordinates(); } public void setRaceBoundCoordinates() { xpoints = new double[this.raceBoundaries.size()]; ypoints = new double[this.raceBoundaries.size()]; for (int i = 0; i < raceBoundaries.size(); i++) { GraphCoordinate coord = map.convertGPS(raceBoundaries.get(i)); xpoints[i] = coord.getX(); ypoints[i] = coord.getY(); } } /** * Set the Canvas to resizable. * * @return That the Canvas is resizable. */ @Override public boolean isResizable() { return true; } /** * Returns the preferred width of the Canvas * * @param width of canvas * @return Returns the width of the Canvas */ @Override public double prefWidth(double width) { return getWidth(); } /** * Returns the preferred height of the Canvas * * @param height of canvas * @return Returns the height of the Canvas */ @Override public double prefHeight(double height) { return getHeight(); } }