diff --git a/src/main/java/seng302/controllers/CanvasController.java b/src/main/java/seng302/controllers/CanvasController.java index c0b20e26..e36fd052 100644 --- a/src/main/java/seng302/controllers/CanvasController.java +++ b/src/main/java/seng302/controllers/CanvasController.java @@ -1,7 +1,9 @@ package seng302.controllers; import javafx.animation.*; +import javafx.beans.property.SimpleDoubleProperty; import javafx.fxml.FXML; +import javafx.scene.Group; import javafx.scene.canvas.Canvas; import javafx.scene.canvas.GraphicsContext; import javafx.scene.layout.AnchorPane; @@ -14,6 +16,7 @@ import seng302.models.mark.Mark; import seng302.models.mark.MarkType; import seng302.models.mark.SingleMark; +import java.sql.Time; import java.util.*; /** @@ -27,6 +30,7 @@ public class CanvasController { private RaceViewController raceViewController; private ResizableCanvas canvas; + private Group group; private GraphicsContext gc; private final double ORIGIN_LAT = 32.321504; @@ -38,30 +42,51 @@ public class CanvasController { } public void initialize() { + raceViewController = new RaceViewController(); canvas = new ResizableCanvas(); + group = new Group(); + canvasPane.getChildren().add(canvas); + canvasPane.getChildren().add(group); // Bind canvas size to stack pane size. - canvas.widthProperty().bind(canvasPane.widthProperty()); - canvas.heightProperty().bind(canvasPane.heightProperty()); + canvas.widthProperty().bind(new SimpleDoubleProperty(1000)); + canvas.heightProperty().bind(new SimpleDoubleProperty(1000)); + group.minWidth(1000); + group.minHeight(1000); +// canvas.widthProperty().bind(canvasPane.widthProperty()); +// canvas.heightProperty().bind(canvasPane.heightProperty()); +// group.minWidth(canvas.getWidth()); +// group.minHeight(canvas.getHeight()); + + + } + + + public void setUpBoats(){ + gc = canvas.getGraphicsContext2D(); - - + gc.save(); + gc.setFill(Color.SKYBLUE); + gc.fillRect(0,0, 1000,1000); + gc.restore(); + drawBoats(); + drawCourse(); + drawFps(12); // overriding the handle so that it can clean canvas and redraw boats and course marks AnimationTimer timer = new AnimationTimer() { private long lastUpdate = 0; private long lastFpsUpdate = 0; private int lastFpsCount = 0; private int fpsCount = 0; + boolean done = true; @Override public void handle(long now) { if (true){ //if statement for limiting refresh rate if needed - gc.clearRect(0, 0, canvas.getWidth(),canvas.getHeight()); - gc.setFill(Color.SKYBLUE); - gc.fillRect(0,0,canvas.getWidth(),canvas.getHeight()); - drawCourse(); - drawBoats(); - drawFps(lastFpsCount); +// gc.clearRect(0, 0, canvas.getWidth(),canvas.getHeight()); +// gc.setFill(Color.SKYBLUE); +// gc.fillRect(0,0,canvas.getWidth(),canvas.getHeight()); + // If race has started, draw the boats and play the timeline if (raceViewController.getRace().getRaceTime() > 1){ @@ -129,14 +154,26 @@ public class CanvasController { * Draws all the boats. */ private void drawBoats() { - Map timelineInfos = raceViewController.getTimelineInfos(); - for (Boat boat : timelineInfos.keySet()) { - TimelineInfo timelineInfo = timelineInfos.get(boat); +// Map timelineInfos = raceViewController.getTimelineInfos(); + ArrayList boats = raceViewController.getStartingBoats(); + Double startingY = (ORIGIN_LAT - raceViewController.getRace().getCourse().get(0).getLatitude()) * SCALE; + Double startingX = (ORIGIN_LON - raceViewController.getRace().getCourse().get(0).getLongitude()) * SCALE; - boat.setLocation(timelineInfo.getY().doubleValue(), timelineInfo.getX().doubleValue()); - - drawBoat(boat.getLongitude(), boat.getLatitude(), boat.getColor(), boat.getShortName(), boat.getSpeedInKnots(), boat.getHeading()); + for (Boat boat : boats) { + boat.moveBoatTo(startingX, startingY); + group.getChildren().add(boat.getWake()); + group.getChildren().add(boat.getBoatObject()); + group.getChildren().add(boat.getTeamNameObject()); + group.getChildren().add(boat.getVelocityObject()); +// drawBoat(boat.getLongitude(), boat.getLatitude(), boat.getColor(), boat.getShortName(), boat.getSpeedInKnots(), boat.getHeading()); } +// for (Boat boat : timelineInfos.keySet()) { +// TimelineInfo timelineInfo = timelineInfos.get(boat); +// +// boat.setLocation(timelineInfo.getY().doubleValue(), timelineInfo.getX().doubleValue()); +// +// drawBoat(boat.getLongitude(), boat.getLatitude(), boat.getColor(), boat.getShortName(), boat.getSpeedInKnots(), boat.getHeading()); +// } } /** diff --git a/src/main/java/seng302/controllers/RaceViewController.java b/src/main/java/seng302/controllers/RaceViewController.java index 965fbc7d..83352357 100644 --- a/src/main/java/seng302/controllers/RaceViewController.java +++ b/src/main/java/seng302/controllers/RaceViewController.java @@ -42,6 +42,7 @@ public class RaceViewController { @FXML private CanvasController includedCanvasController; + private ArrayList startingBoats = new ArrayList<>(); private boolean displayAnnotations; private boolean displayFps; private Timeline timerTimeline; @@ -50,24 +51,29 @@ public class RaceViewController { private Race race; public void initialize() { - includedCanvasController.setup(this); RaceController raceController = new RaceController(); raceController.initializeRace(); race = raceController.getRace(); + for (Boat boat : race.getBoats()) { + startingBoats.add(boat); + } +// try{ +// initializeTimelines(); +// } +// catch (Exception e){ +// e.printStackTrace(); +// } + includedCanvasController.setup(this); + includedCanvasController.setUpBoats(); initializeTimer(); initializeSettings(); - try{ - initializeTimelines(); - } - catch (Exception e){ - e.printStackTrace(); - } //set wind direction!!!!!!! can't find another place to put my code --haoming double windDirection = new ConfigParser("/config/config.xml").getWindDirection(); windDirectionText.setText(String.format("%.1f°", windDirection)); windArrowText.setRotate(windDirection); + } private void initializeSettings(){ @@ -114,39 +120,39 @@ public class RaceViewController { */ private void initializeTimelines() { HashMap boat_events = race.getEvents(); - for (Boat boat : boat_events.keySet()) { - // x, y are the real time coordinates - DoubleProperty x = new SimpleDoubleProperty(); - DoubleProperty y = new SimpleDoubleProperty(); - - List keyFrames = new ArrayList<>(); - List events = boat_events.get(boat); - - // iterates all events and convert each event to keyFrame, then add them into a list - for (Event event : events) { - if (event.getIsFinishingEvent()) { - keyFrames.add( - new KeyFrame(Duration.seconds(event.getTime()), - onFinished -> {race.setBoatFinished(boat); handleEvent(event);}, - new KeyValue(x, event.getThisMark().getLatitude()), - new KeyValue(y, event.getThisMark().getLongitude()) - ) - ); - } else { - keyFrames.add( - new KeyFrame(Duration.seconds(event.getTime()), - onFinished ->{ - handleEvent(event); - boat.setHeading(event.getBoatHeading()); - }, - new KeyValue(x, event.getThisMark().getLatitude()), - new KeyValue(y, event.getThisMark().getLongitude()) - ) - ); - } - } - timelineInfos.put(boat, new TimelineInfo(new Timeline(keyFrames.toArray(new KeyFrame[keyFrames.size()])), x, y)); + startingBoats.add(boat); +// // x, y are the real time coordinates +// DoubleProperty x = new SimpleDoubleProperty(); +// DoubleProperty y = new SimpleDoubleProperty(); +// +// List keyFrames = new ArrayList<>(); +// List events = boat_events.get(boat); +// +// // iterates all events and convert each event to keyFrame, then add them into a list +// for (Event event : events) { +// if (event.getIsFinishingEvent()) { +// keyFrames.add( +// new KeyFrame(Duration.seconds(event.getTime()), +// onFinished -> {race.setBoatFinished(boat); handleEvent(event);}, +// new KeyValue(x, event.getThisMark().getLatitude()), +// new KeyValue(y, event.getThisMark().getLongitude()) +// ) +// ); +// } else { +// keyFrames.add( +// new KeyFrame(Duration.seconds(event.getTime()), +// onFinished ->{ +// handleEvent(event); +// boat.setHeading(event.getBoatHeading()); +// }, +// new KeyValue(x, event.getThisMark().getLatitude()), +// new KeyValue(y, event.getThisMark().getLongitude()) +// ) +// ); +// } +// } +// timelineInfos.put(boat, new TimelineInfo(new Timeline(keyFrames.toArray(new KeyFrame[keyFrames.size()])), x, y)); } setRaceDuration(); } @@ -277,4 +283,8 @@ public class RaceViewController { public Map getTimelineInfos() { return timelineInfos; } + + public ArrayList getStartingBoats(){ + return startingBoats; + } } \ No newline at end of file diff --git a/src/main/java/seng302/models/Boat.java b/src/main/java/seng302/models/Boat.java index 0973ad38..f39f9e3b 100644 --- a/src/main/java/seng302/models/Boat.java +++ b/src/main/java/seng302/models/Boat.java @@ -1,12 +1,24 @@ package seng302.models; import javafx.scene.paint.Color; +import javafx.scene.shape.Polygon; +import javafx.scene.text.Text; +import javafx.scene.transform.Rotate; +import javafx.scene.transform.Translate; /** * Represents a boat in the race. */ public class Boat { + private static final double TEAMNAME_X_OFFSET = 15d; + private static final double TEAMNAME_Y_OFFSET = -20d; + private static final double VELOCITY_X_OFFSET = 15d; + private static final double VELOCITY_Y_OFFSET = -10d; + private static final double VELOCITY_WAKE_RATIO = 2d; //Ratio for deciding how long the wake will be wrt velocity + private static final double BOAT_HEIGHT = 15d; + private static final double BOAT_WIDTH = 10d; + private String teamName; // The name of the team, this is also the name of the boat private double velocity; // In meters/second private double lat; // Boats position @@ -17,6 +29,12 @@ public class Boat { private double heading; private String shortName; + //Graphical + private Polygon boatObject; + private Polygon wake; + private Text teamNameObject; + private Text velocityObject; + public Boat(String teamName) { this.teamName = teamName; this.velocity = 10; // Default velocity @@ -39,6 +57,13 @@ public class Boat { this.distanceToNextMark = 0.0; this.color = Colors.getColor(); this.shortName = shortName; + this.boatObject = new Polygon(); + this.boatObject.getPoints().addAll(BOAT_WIDTH /2,0.0, + BOAT_WIDTH, BOAT_HEIGHT, + 0.0, BOAT_HEIGHT); + createWake(); + this.teamNameObject = new Text(shortName); + this.velocityObject = new Text(Double.toString(boatVelocity) + "ms"); } /** @@ -117,6 +142,11 @@ public class Boat { } public void setHeading(double heading){ + boatObject.getTransforms().clear(); + wake.getTransforms().clear(); + wake.getTransforms().add(new Translate(0, BOAT_HEIGHT)); + wake.getTransforms().add(new Rotate(heading, BOAT_WIDTH/2, -BOAT_HEIGHT)); + boatObject.getTransforms().add(new Rotate(heading, BOAT_WIDTH/2, 0)); this.heading = heading; } @@ -127,4 +157,76 @@ public class Boat { public String getShortName(){ return this.shortName; } + + + /** + * Moves the boat and its children annotations from its current coordinates by specified amounts. + * @param x The amount to move the X coordinate by + * @param y The amount to move the Y coordinate by + */ + void moveBoatBy(Double x, Double y) { + boatObject.setLayoutX(boatObject.getLayoutX() + x); + boatObject.setLayoutY(boatObject.getLayoutY() + y); + boatObject.relocate(boatObject.getLayoutX(), boatObject.getLayoutY()); + + teamNameObject.setX(teamNameObject.getX() + x); + teamNameObject.setY(teamNameObject.getY() + y); + teamNameObject.relocate(teamNameObject.getX(), teamNameObject.getY()); + + velocityObject.setX(velocityObject.getX() + x); + velocityObject.setY(velocityObject.getY() + y); + velocityObject.relocate(velocityObject.getX(), velocityObject.getY()); + + + wake.setLayoutX(wake.getLayoutX() + x); + wake.setLayoutY(wake.getLayoutY() + y); + wake.relocate(wake.getLayoutX(), wake.getLayoutY()); + } + + /** + * Moves the boat and its children annotations to coordinates specified + * @param x The X coordinate to move the boat to + * @param y The Y coordinate to move the boat to + */ + public void moveBoatTo(Double x, Double y) { + boatObject.setLayoutX(x); + boatObject.setLayoutY(y); + boatObject.relocate(boatObject.getLayoutX(), boatObject.getLayoutY()); + + teamNameObject.setX(x + TEAMNAME_X_OFFSET); + teamNameObject.setY(y + TEAMNAME_Y_OFFSET); + teamNameObject.relocate(teamNameObject.getX(), teamNameObject.getY()); + + velocityObject.setX(x + VELOCITY_X_OFFSET); + velocityObject.setY(y + VELOCITY_Y_OFFSET); + velocityObject.relocate(velocityObject.getX(), velocityObject.getY()); + + wake.setLayoutX(x); + wake.setLayoutY(y); + wake.relocate(wake.getLayoutX(), wake.getLayoutY()); + } + + private void createWake(){ + wake = new Polygon(); + wake.setFill(Color.LIGHTSKYBLUE); + wake.getPoints().addAll(5.0,0.0, + 10.0, velocity * VELOCITY_WAKE_RATIO, + 0.0, velocity * VELOCITY_WAKE_RATIO); + } + + public Polygon getWake() { + return wake; + } + + public Polygon getBoatObject() { + return boatObject; + } + + public Text getTeamNameObject() { + return teamNameObject; + } + + public Text getVelocityObject() { + return velocityObject; + } } \ No newline at end of file diff --git a/src/main/java/seng302/models/Event.java b/src/main/java/seng302/models/Event.java index e803845d..df298741 100644 --- a/src/main/java/seng302/models/Event.java +++ b/src/main/java/seng302/models/Event.java @@ -16,7 +16,7 @@ public class Event { private Mark mark1; // This mark private Mark mark2; // Next mark private int markPosInRace; // the position of the current mark in the race course - + private double heading; private final double ORIGIN_LAT = 32.320504; private final double ORIGIN_LON = -64.857063; private final double SCALE = 16000; @@ -36,6 +36,8 @@ public class Event { this.mark1 = mark1; this.mark2 = mark2; this.markPosInRace = markPosInRace; + this.heading = angleFromCoordinate(mark1, mark2); + } /** @@ -92,7 +94,7 @@ public class Event { if (this.isFinishingEvent) { return (this.getTimeString() + ", " + this.getBoat().getTeamName() + " finished the race"); } - System.out.println(this.getDistanceBetweenMarks()); +// System.out.println(this.getDistanceBetweenMarks()); return (this.getTimeString() + ", " + this.getBoat().getTeamName() + " passed " + this.mark1.getName() + " going heading " + this.getBoatHeading() + "°"); } @@ -138,6 +140,30 @@ public class Event { } + /** + * Calculates the angle between to angular co-ordinates on a sphere. + * + * @param geoPointOne first geographical location + * @param geoPointTwo second geographical location + * @return the angle from point one to point two + */ + private Double angleFromCoordinate(Mark geoPointOne, Mark geoPointTwo) { + if (geoPointTwo == null) + return null; + + double x1 = geoPointOne.getLatitude(); + double y1 = -geoPointOne.getLongitude(); + double x2 = geoPointTwo.getLatitude(); + double y2 = -geoPointTwo.getLongitude(); + + return Math.toDegrees(Math.atan2(x2-x1, y2-y1)); + + } + + public double getHeading() { + return heading; + } + public Mark getThisMark() { return this.mark1; }