From 15ded667fe9095c99a5fc116f1fb57d95f42d6e2 Mon Sep 17 00:00:00 2001 From: Kusal Ekanayake Date: Tue, 11 Apr 2017 17:46:02 +1200 Subject: [PATCH 1/5] Started to implement the group over the canvas in the code. Removed basic boat redrawing and timeline and replaced with boats being placed into a group and given coordinates. --- .../seng302/controllers/CanvasController.java | 69 +++++++++--- .../controllers/RaceViewController.java | 88 ++++++++------- src/main/java/seng302/models/Boat.java | 102 ++++++++++++++++++ src/main/java/seng302/models/Event.java | 30 +++++- 4 files changed, 232 insertions(+), 57 deletions(-) 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; } From b5129c5c806d06babf54f7dd0bb402151574e96f Mon Sep 17 00:00:00 2001 From: Calum Date: Thu, 20 Apr 2017 19:06:32 +1200 Subject: [PATCH 2/5] Moved the canvas drawing implementation from team27's codebase to team13's. #story30b --- .../seng302/controllers/CanvasController.java | 303 ++++++++++++------ .../java/seng302/models/mark/GateMark.java | 16 +- src/main/java/seng302/models/mark/Mark.java | 89 +++++ .../java/seng302/models/mark/MarkType.java | 2 +- .../seng302/models/parsers/CourseParser.java | 6 +- .../java/seng302/models/mark/MarkTest.java | 4 +- .../models/parsers/CourseParserTest.java | 2 +- 7 files changed, 323 insertions(+), 99 deletions(-) diff --git a/src/main/java/seng302/controllers/CanvasController.java b/src/main/java/seng302/controllers/CanvasController.java index e36fd052..0499eb05 100644 --- a/src/main/java/seng302/controllers/CanvasController.java +++ b/src/main/java/seng302/controllers/CanvasController.java @@ -9,8 +9,8 @@ import javafx.scene.canvas.GraphicsContext; import javafx.scene.layout.AnchorPane; import javafx.scene.paint.Color; import javafx.scene.text.Font; +import javafx.util.Pair; import seng302.models.Boat; -import seng302.models.TimelineInfo; import seng302.models.mark.GateMark; import seng302.models.mark.Mark; import seng302.models.mark.MarkType; @@ -33,9 +33,27 @@ public class CanvasController { private Group group; private GraphicsContext gc; - private final double ORIGIN_LAT = 32.321504; - private final double ORIGIN_LON = -64.857063; - private final int SCALE = 16000; + private final int MARK_SIZE = 10; + private final int BUFFER_SIZE = 25; + private final int CANVAS_SIZE = 1000; + private final int LHS_BUFFER = BUFFER_SIZE; + private final int RHS_BUFFER = BUFFER_SIZE + MARK_SIZE / 2; + private final int TOP_BUFFER = BUFFER_SIZE; + private final int BOT_BUFFER = TOP_BUFFER + MARK_SIZE / 2; + + private double distanceScaleFactor; + private ScaleDirection scaleDirection; + private Mark minLatPoint; + private Mark minLonPoint; + private Mark maxLatPoint; + private Mark maxLonPoint; + private int referencePointX; + private int referencePointY; + + private enum ScaleDirection { + HORIZONTAL, + VERTICAL + } public void setup(RaceViewController raceViewController){ this.raceViewController = raceViewController; @@ -49,10 +67,10 @@ public class CanvasController { canvasPane.getChildren().add(canvas); canvasPane.getChildren().add(group); // Bind canvas size to stack pane size. - canvas.widthProperty().bind(new SimpleDoubleProperty(1000)); - canvas.heightProperty().bind(new SimpleDoubleProperty(1000)); - group.minWidth(1000); - group.minHeight(1000); + canvas.widthProperty().bind(new SimpleDoubleProperty(CANVAS_SIZE)); + canvas.heightProperty().bind(new SimpleDoubleProperty(CANVAS_SIZE)); + group.minWidth(CANVAS_SIZE); + group.minHeight(CANVAS_SIZE); // canvas.widthProperty().bind(canvasPane.widthProperty()); // canvas.heightProperty().bind(canvasPane.heightProperty()); // group.minWidth(canvas.getWidth()); @@ -67,10 +85,16 @@ public class CanvasController { gc = canvas.getGraphicsContext2D(); gc.save(); gc.setFill(Color.SKYBLUE); - gc.fillRect(0,0, 1000,1000); + gc.fillRect(0,0, CANVAS_SIZE,CANVAS_SIZE); gc.restore(); - drawBoats(); drawCourse(); + for (Mark m : raceViewController.getRace().getCourse()) + { + System.out.println("MARK NAME - " + m.getName()); + System.out.println("X LOCATION - " + m.getX()); + System.out.println("Y LOCATION - " + m.getY()); + } + drawBoats(); drawFps(12); // overriding the handle so that it can clean canvas and redraw boats and course marks AnimationTimer timer = new AnimationTimer() { @@ -156,8 +180,8 @@ public class CanvasController { private void drawBoats() { // 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; + Double startingX = (double) raceViewController.getRace().getCourse().get(0).getX(); + Double startingY = (double) raceViewController.getRace().getCourse().get(0).getY(); for (Boat boat : boats) { boat.moveBoatTo(startingX, startingY); @@ -167,75 +191,8 @@ public class CanvasController { 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()); -// } } - /** - * Draw the wake line behind a boat - * @param gc The graphics context used for drawing the wake - * @param x the x position of the boat - * @param y the y position of the boat - * @param speed the speed of the boat - * @param color the color of the wake line - * @param heading the heading of the boat - */ - private void drawWake(GraphicsContext gc, double x, double y, double speed, Color color, double heading){ - double angle = Math.toRadians(heading); - speed = speed * 2; - Point newP = new Point(0, speed); - newP.rotate(angle); - - gc.setStroke(color); - gc.setLineWidth(1.0); - gc.strokeLine(x, y, newP.x + x, newP.y + y); - } - - /** - * Draws a boat with given (x, y) position in the given color - * - * @param lat - * @param lon - * @param color - * @param name - * @param speed - */ - private void drawBoat(double lat, double lon, Color color, String name, double speed, double heading) { - // Latitude - double x = (lon - ORIGIN_LON) * SCALE; - double y = (ORIGIN_LAT - lat) * SCALE; - - gc.setFill(color); - - if (raceViewController.isDisplayAnnotations()) { - // Set boat text - gc.setFont(new Font(14)); - gc.setLineWidth(3); - gc.fillText(name + ", " + speed + " knots", x + 15, y + 15); - } -// double diameter = 9; -// gc.fillOval(x, y, diameter, diameter); - double angle = Math.toRadians(heading); - - Point p1 = new Point(0, -15); // apex point - Point p2 = new Point(7, 4); // base point - Point p3 = new Point(-7, 4); // base point - p1.rotate(angle); - p2.rotate(angle); - p3.rotate(angle); - double[] xx = new double[] {p1.x + x, p2.x + x, x, p3.x + x}; - double[] yy = new double[] {p1.y + y, p2.y + y, y, p3.y + y}; - gc.fillPolygon(xx, yy, 4); - - if (raceViewController.isDisplayAnnotations()){ - drawWake(gc, x, y, speed, color, heading); - } - } /** * Inner class for creating point so that you can rotate it around origin point. @@ -262,10 +219,11 @@ public class CanvasController { * Draws the course. */ private void drawCourse() { + fitToCanvas(); for (Mark mark : raceViewController.getRace().getCourse()) { if (mark.getMarkType() == MarkType.SINGLE_MARK) { drawSingleMark((SingleMark) mark, Color.BLACK); - } else if (mark.getMarkType() == MarkType.GATE_MARK) { + } else { drawGateMark((GateMark) mark); } } @@ -277,11 +235,8 @@ public class CanvasController { * @param singleMark */ private void drawSingleMark(SingleMark singleMark, Color color) { - double x = (singleMark.getLongitude() - ORIGIN_LON) * SCALE; - double y = (ORIGIN_LAT - singleMark.getLatitude()) * SCALE; - gc.setFill(color); - gc.fillRect(x,y,5.5,5.5); + gc.fillOval(singleMark.getX(), singleMark.getY(),MARK_SIZE,MARK_SIZE); } /** @@ -304,17 +259,181 @@ public class CanvasController { drawSingleMark(gateMark.getSingleMark2(), color); GraphicsContext gc = canvas.getGraphicsContext2D(); - + gc.save(); gc.setStroke(color); + if (gateMark.getMarkType() == MarkType.OPEN_GATE) + gc.setLineDashes(3, 5); - // Convert lat/lon to x,y - double x1 = (gateMark.getSingleMark1().getLongitude()- ORIGIN_LON) * SCALE; - double y1 = (ORIGIN_LAT - gateMark.getSingleMark1().getLatitude()) * SCALE; + gc.setLineWidth(2); + gc.strokeLine( + gateMark.getSingleMark1().getX() + MARK_SIZE / 2, + gateMark.getSingleMark1().getY() + MARK_SIZE / 2, + gateMark.getSingleMark2().getX() + MARK_SIZE / 2, + gateMark.getSingleMark2().getY() + MARK_SIZE / 2 + ); + gc.restore(); + } - double x2 = (gateMark.getSingleMark2().getLongitude() - ORIGIN_LON) * SCALE; - double y2 = (ORIGIN_LAT - gateMark.getSingleMark2().getLatitude()) * SCALE; + /** + * Calculates x and y location for every marker that fits it to the canvas the race will be drawn on. + */ + private void fitToCanvas() { + findMinMaxPoint(); + double minLonToMaxLon = scaleRaceExtremities(); + calculateReferencePointLocation(minLonToMaxLon); + givePointsXY(); + } - gc.setLineWidth(1); - gc.strokeLine(x1, y1, x2, y2); + /** + * Sets the class variables minLatPoint, maxLatPoint, minLonPoint, maxLonPoint to the marker with the leftmost + * marker, rightmost marker, southern most marker and northern most marker respectively. + */ + private void findMinMaxPoint() { + ArrayList sortedPoints = new ArrayList<>(); + for (Mark mark : raceViewController.getRace().getCourse()) + { + if (mark.getMarkType() == MarkType.SINGLE_MARK) + sortedPoints.add(mark); + else { + sortedPoints.add(((GateMark) mark).getSingleMark1()); + sortedPoints.add(((GateMark) mark).getSingleMark2()); + } + } + sortedPoints.sort(Comparator.comparingDouble(Mark::getLatitude)); + minLatPoint = sortedPoints.get(0); + maxLatPoint = sortedPoints.get(sortedPoints.size()-1); + + sortedPoints.sort(Comparator.comparingDouble(Mark::getLongitude)); + //If the course is on a point on the earth where longitudes wrap around. + // TODO: 30/03/17 cir27 - Correctly account for longitude wrapping around. + if (sortedPoints.get(sortedPoints.size()-1).getLongitude() - sortedPoints.get(0).getLongitude() > 180) + Collections.reverse(sortedPoints); + minLonPoint = sortedPoints.get(0); + maxLonPoint = sortedPoints.get(sortedPoints.size()-1); + } + + /** + * 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) { + Mark referencePoint = minLatPoint; + double referenceAngle; + double mapWidth = canvas.getWidth(); + double mapHeight = canvas.getHeight(); + + if (scaleDirection == ScaleDirection.HORIZONTAL) { + referenceAngle = Mark.calculateHeadingRad(referencePoint, minLonPoint) - (Math.PI * (3/4)); + referencePointX = LHS_BUFFER + (int) Math.round(distanceScaleFactor * Math.cos(referenceAngle) * Mark.calculateDistance(referencePoint, minLonPoint)); + + referenceAngle = Mark.calculateHeadingRad(referencePoint, maxLatPoint); + if (referenceAngle > (Math.PI / 2)) { + referenceAngle = (Math.PI * 2) - referenceAngle; + } + referencePointY = (int) Math.round(mapHeight - (TOP_BUFFER + BOT_BUFFER)); + referencePointY -= (int) Math.round(distanceScaleFactor * Math.cos(referenceAngle) * Mark.calculateDistance(referencePoint, maxLatPoint)); + referencePointY = (int) Math.round(referencePointY / 2d); + referencePointY += TOP_BUFFER; + referencePointY += (int) Math.round(distanceScaleFactor * Math.cos(referenceAngle) * Mark.calculateDistance(referencePoint, maxLatPoint)); + } else { + referencePointY = (int) Math.round(mapHeight - BOT_BUFFER); + + referenceAngle = (Math.PI * 2) - Mark.calculateHeadingRad(referencePoint, minLonPoint); + + referencePointX = LHS_BUFFER; + referencePointX += (int) Math.round(distanceScaleFactor * Math.sin(referenceAngle) * Mark.calculateDistance(referencePoint, minLonPoint)); + referencePointX += (int) Math.round(((mapWidth - (LHS_BUFFER + RHS_BUFFER)) - (minLonToMaxLon * distanceScaleFactor)) / 2); + } + referencePoint.setX(referencePointX); + referencePoint.setY(referencePointY); + System.out.println("REF POINT = " + referencePoint.getName()); + System.out.println(referencePointX); + System.out.println(referencePointY); + } + + /** + * 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 = Mark.calculateHeadingRad(minLatPoint, maxLatPoint); + if (vertAngle > Math.PI) + vertAngle = (2 * Math.PI) - vertAngle; + double vertDistance = Math.cos(vertAngle) * Mark.calculateDistance(minLatPoint, maxLatPoint); + + double horiAngle = Mark.calculateHeadingRad(minLonPoint, maxLonPoint); + if (horiAngle <= (Math.PI / 2)) + horiAngle = (Math.PI / 2) - horiAngle; + else + horiAngle = horiAngle - (Math.PI / 2); + double horiDistance = Math.cos(horiAngle) * Mark.calculateDistance(minLonPoint, maxLonPoint); + + double vertScale = (canvas.getHeight() - (TOP_BUFFER + BOT_BUFFER)) / vertDistance; + + if ((horiDistance * vertScale) > (canvas.getWidth() - (RHS_BUFFER + LHS_BUFFER))) { + distanceScaleFactor = (canvas.getWidth() - (RHS_BUFFER + LHS_BUFFER)) / horiDistance; + scaleDirection = ScaleDirection.HORIZONTAL; + } else { + distanceScaleFactor = vertScale; + scaleDirection = ScaleDirection.VERTICAL; + } + return horiDistance; + } + + /** + * Give all markers in the course an x,y location relative to a given reference with a known x,y location. Distances + * are scaled according to the distanceScaleFactor variable. + */ + private void givePointsXY() { + Pair canvasLocation; + ArrayList allPoints = new ArrayList<>(raceViewController.getRace().getCourse()); + + for (Mark mark : allPoints) { + if (mark.getMarkType() != MarkType.SINGLE_MARK) { + GateMark gateMark = (GateMark) mark; + + canvasLocation = findScaledXY(gateMark.getSingleMark1()); + gateMark.getSingleMark1().setX(canvasLocation.getKey()); + gateMark.getSingleMark1().setY(canvasLocation.getValue()); + + canvasLocation = findScaledXY(gateMark.getSingleMark2()); + gateMark.getSingleMark2().setX(canvasLocation.getKey()); + gateMark.getSingleMark2().setY(canvasLocation.getValue()); + } + if (mark.getMarkType() == MarkType.CLOSED_GATE) + ((GateMark) mark).assignXYCentered(); + else { + canvasLocation = findScaledXY(mark); + mark.setX(canvasLocation.getKey()); + mark.setY(canvasLocation.getValue()); + } + } + } + + private Pair findScaledXY (Mark unscaled) { + double distanceFromReference; + double angleFromReference; + int yAxisLocation; + int xAxisLocation; + + angleFromReference = Mark.calculateHeadingRad(minLatPoint, unscaled); + distanceFromReference = Mark.calculateDistance(minLatPoint, unscaled); + //angleFromReference = Mark.calculateHeadingRad(lon1, lon2, lat1, lat2); + //distanceFromReference = Mark.calculateDistance(lon1, lon2, lat1, lat2); + + if (angleFromReference > (Math.PI / 2)) { + angleFromReference = (Math.PI * 2) - angleFromReference; + xAxisLocation = referencePointX; + xAxisLocation -= (int) Math.round(distanceScaleFactor * Math.sin(angleFromReference) * distanceFromReference); + } else { + xAxisLocation = referencePointX; + xAxisLocation += (int) Math.round(distanceScaleFactor * Math.sin(angleFromReference) * distanceFromReference); + } + yAxisLocation = referencePointY; + yAxisLocation -= (int) Math.round(distanceScaleFactor * Math.cos(angleFromReference) * distanceFromReference); + + return new Pair<>(xAxisLocation, yAxisLocation); } } \ No newline at end of file diff --git a/src/main/java/seng302/models/mark/GateMark.java b/src/main/java/seng302/models/mark/GateMark.java index 2b152e65..2dfb9fdd 100644 --- a/src/main/java/seng302/models/mark/GateMark.java +++ b/src/main/java/seng302/models/mark/GateMark.java @@ -16,8 +16,8 @@ public class GateMark extends Mark { * @param singleMark1 one single mark inside of the gate mark * @param singleMark2 the second mark inside of the gate mark */ - public GateMark(String name, SingleMark singleMark1, SingleMark singleMark2, double latitude, double longitude) { - super(name, MarkType.GATE_MARK, latitude, longitude); + public GateMark(String name, MarkType type, SingleMark singleMark1, SingleMark singleMark2, double latitude, double longitude) { + super(name, type, latitude, longitude); this.singleMark1 = singleMark1; this.singleMark2 = singleMark2; } @@ -47,4 +47,16 @@ public class GateMark extends Mark { //return (this.getSingleMark1().getLongitude() + this.getSingleMark2().getLongitude()) / 2; return (this.getSingleMark1().getLongitude()); } + + public void assignXYCentered () { + System.out.println("POSSIBLE GOOF " + xValue + " " + yValue); + System.out.println(singleMark1.getX() + " " + singleMark1.getY()); + System.out.println(singleMark2.getX() + " " + singleMark2.getY()); + double dx = singleMark2.getX() - singleMark1.getX(); + System.out.println("dx + " + dx); + double dy = singleMark2.getY() - singleMark1.getY(); + xValue = (int) Math.round(singleMark1.getX() + dx / 2); + yValue = (int) Math.round(singleMark1.getY() + dy / 2); + System.out.println("PROBABLE GAAF " + xValue + " " + yValue); + } } diff --git a/src/main/java/seng302/models/mark/Mark.java b/src/main/java/seng302/models/mark/Mark.java index 3e635856..44e9877b 100644 --- a/src/main/java/seng302/models/mark/Mark.java +++ b/src/main/java/seng302/models/mark/Mark.java @@ -10,6 +10,8 @@ public abstract class Mark { private MarkType markType; private double latitude; private double longitude; + Integer xValue; + Integer yValue; /** * Create a mark instance by passing its name and type @@ -28,6 +30,76 @@ public abstract class Mark { this.longitude = longitude; } + /** + * Calculated the heading in radians from first Mark to the second Mark. + * + * @param pointOne First Mark + * @param pointTwo Second Mark + * @return Heading in radians + */ + public static Double calculateHeadingRad(Mark pointOne, Mark pointTwo) { + Double longitude1 = pointOne.getLongitude(); + Double longitude2 = pointTwo.getLongitude(); + Double latitude1 = pointOne.getLatitude(); + Double latitude2 = pointTwo.getLatitude(); + return calculateHeadingRad(longitude1, longitude2, latitude1, latitude2); + } + + /** + * Calculate the heading in radians from geographical location with latitude1, longitude 1 to geographical + * latitude2, longitude 2 + * @param longitude1 Longitude of first point in degrees + * @param longitude2 Longitude of second point in degrees + * @param latitude1 Latitude of first point in degrees + * @param latitude2 Latitude of first point in degrees + * @return Heading in radians + */ + public static double calculateHeadingRad (Double longitude1, Double longitude2, Double latitude1, Double latitude2) { + latitude1 = Math.toRadians(latitude1); + latitude2 = Math.toRadians(latitude2); + Double longDiff= Math.toRadians(longitude2-longitude1); + Double y = Math.sin(longDiff)*Math.cos(latitude2); + Double x = Math.cos(latitude1)*Math.sin(latitude2)-Math.sin(latitude1)*Math.cos(latitude2)*Math.cos(longDiff); + return Math.atan2(y, x); + } + + /** + * Calculates the distance in meters from the first Mark to a second Mark + * + * @param pointOne First Mark + * @param pointTwo Second Mark + * @return Distance in meters + */ + public static Double calculateDistance(Mark pointOne, Mark pointTwo) { + Double longitude1 = pointOne.getLongitude(); + Double longitude2 = pointTwo.getLongitude(); + Double latitude1 = pointOne.getLatitude(); + Double latitude2 = pointTwo.getLatitude(); + return calculateDistance(longitude1, longitude2, latitude1, latitude2); + } + + /** + * Calculate the distance in meters from geographical location with latitude1, longitude 1 to geographical + * latitude2, longitude 2 + * + * @param longitude1 Longitude of first point in degrees + * @param longitude2 Longitude of second point in degrees + * @param latitude1 Latitude of first point in degrees + * @param latitude2 Latitude of first point in degrees + * @return Distance in meters + */ + public static Double calculateDistance (Double longitude1, Double longitude2, Double latitude1, Double latitude2) { + Double theta = longitude1 - longitude2; + Double dist = Math.sin(Math.toRadians(latitude1)) * Math.sin(Math.toRadians(latitude2)) + + Math.cos(Math.toRadians(latitude1)) * Math.cos(Math.toRadians(latitude2)) * + Math.cos(Math.toRadians(theta)); + dist = Math.acos(dist); + dist = Math.toDegrees(dist); + dist = dist * 60 * 1.1508; //nautical mile (distance between two degrees) * (degrees in a minute) + dist = dist * 1609.344; //ratio of miles to metres + return dist; + } + public String getName() { return name; } @@ -51,4 +123,21 @@ public abstract class Mark { public double getLongitude() { return longitude; } + + public int getX () { + return xValue; + } + + public int getY () { + return yValue; + } + + public void setX (int x) { + this.xValue = x; + } + + public void setY (int y) { + this.yValue = y; + } + } diff --git a/src/main/java/seng302/models/mark/MarkType.java b/src/main/java/seng302/models/mark/MarkType.java index 3de5cba3..4ac6a9e3 100644 --- a/src/main/java/seng302/models/mark/MarkType.java +++ b/src/main/java/seng302/models/mark/MarkType.java @@ -5,5 +5,5 @@ package seng302.models.mark; * Created by Haoming Yin (hyi25) on 17/3/17. */ public enum MarkType { - SINGLE_MARK, GATE_MARK + SINGLE_MARK, OPEN_GATE, CLOSED_GATE } diff --git a/src/main/java/seng302/models/parsers/CourseParser.java b/src/main/java/seng302/models/parsers/CourseParser.java index 0ad0c471..04a40e0a 100644 --- a/src/main/java/seng302/models/parsers/CourseParser.java +++ b/src/main/java/seng302/models/parsers/CourseParser.java @@ -65,7 +65,11 @@ public class CourseParser extends FileParser { String name = element.getElementsByTagName("name").item(0).getTextContent(); SingleMark mark1 = generateSingleMark(element.getElementsByTagName("mark").item(0)); SingleMark mark2 = generateSingleMark(element.getElementsByTagName("mark").item(1)); - GateMark gateMark = new GateMark(name, mark1, mark2, mark1.getLatitude(), mark1.getLongitude()); + GateMark gateMark; + if (name.equals("Start") || name.equals("Finish")) + gateMark = new GateMark(name, MarkType.CLOSED_GATE, mark1, mark2, mark1.getLatitude(), mark1.getLongitude()); + else + gateMark = new GateMark(name, MarkType.OPEN_GATE, mark1, mark2, mark1.getLatitude(), mark1.getLongitude()); marks.put(name, gateMark); } } diff --git a/src/test/java/seng302/models/mark/MarkTest.java b/src/test/java/seng302/models/mark/MarkTest.java index f1943c64..b48f5818 100644 --- a/src/test/java/seng302/models/mark/MarkTest.java +++ b/src/test/java/seng302/models/mark/MarkTest.java @@ -18,7 +18,7 @@ public class MarkTest { public void setUp() throws Exception { this.singleMark1 = new SingleMark("testMark_SM1", 12.23234, -34.342); this.singleMark2 = new SingleMark("testMark_SM2", 12.23239, -34.352); - this.gateMark = new GateMark("testMark_GM", singleMark1, singleMark2, singleMark1.getLatitude(), singleMark2.getLongitude()); + this.gateMark = new GateMark("testMark_GM", MarkType.OPEN_GATE, singleMark1, singleMark2, singleMark1.getLatitude(), singleMark2.getLongitude()); } @Test @@ -30,7 +30,7 @@ public class MarkTest { @Test public void getMarkType() throws Exception { assertTrue(this.singleMark2.getMarkType() == MarkType.SINGLE_MARK); - assertTrue(this.gateMark.getMarkType() == MarkType.GATE_MARK); + assertTrue(this.gateMark.getMarkType() == MarkType.OPEN_GATE); } @Test diff --git a/src/test/java/seng302/models/parsers/CourseParserTest.java b/src/test/java/seng302/models/parsers/CourseParserTest.java index 865caec6..201c1b4b 100644 --- a/src/test/java/seng302/models/parsers/CourseParserTest.java +++ b/src/test/java/seng302/models/parsers/CourseParserTest.java @@ -25,7 +25,7 @@ public class CourseParserTest { public void getGates() throws Exception { ArrayList course = cp.getCourse(); - assertTrue(MarkType.GATE_MARK == course.get(0).getMarkType()); + assertTrue(MarkType.OPEN_GATE == course.get(0).getMarkType()); GateMark gateMark1 = (GateMark) course.get(0); assertEquals(32.293771, gateMark1.getSingleMark2().getLatitude(), 0.00000001); From 46037b5aea3b308bfa7a2854fbd22e9cb17207bb Mon Sep 17 00:00:00 2001 From: Calum Date: Mon, 24 Apr 2017 23:06:30 +1200 Subject: [PATCH 3/5] Refactored Boat class to better fit the MVC model by moving all GUI parts to BoatPolygon. Changed the way animation works so that it will work with a constantly updated set of lats and lons. TODO - Change Mark class to no longer store XY pixel data. TODO - Add in a timer force updates boat position if a packet has not been recieved for a while. #story30b #story30c #implement #refactor --- .../seng302/controllers/CanvasController.java | 264 ++++++++++++------ .../controllers/RaceViewController.java | 9 +- src/main/java/seng302/models/Boat.java | 134 ++------- src/main/java/seng302/models/BoatPolygon.java | 171 ++++++++++++ src/main/java/seng302/models/Colors.java | 7 +- src/main/java/seng302/models/Race.java | 1 + src/main/java/seng302/models/mark/Mark.java | 8 +- src/test/java/seng302/ColorsTest.java | 32 +-- 8 files changed, 392 insertions(+), 234 deletions(-) create mode 100644 src/main/java/seng302/models/BoatPolygon.java diff --git a/src/main/java/seng302/controllers/CanvasController.java b/src/main/java/seng302/controllers/CanvasController.java index 0499eb05..03db02f1 100644 --- a/src/main/java/seng302/controllers/CanvasController.java +++ b/src/main/java/seng302/controllers/CanvasController.java @@ -3,6 +3,7 @@ package seng302.controllers; import javafx.animation.*; import javafx.beans.property.SimpleDoubleProperty; import javafx.fxml.FXML; +import javafx.geometry.Point2D; import javafx.scene.Group; import javafx.scene.canvas.Canvas; import javafx.scene.canvas.GraphicsContext; @@ -11,6 +12,8 @@ import javafx.scene.paint.Color; import javafx.scene.text.Font; import javafx.util.Pair; import seng302.models.Boat; +import seng302.models.BoatPolygon; +import seng302.models.Colors; import seng302.models.mark.GateMark; import seng302.models.mark.Mark; import seng302.models.mark.MarkType; @@ -32,14 +35,17 @@ public class CanvasController { private ResizableCanvas canvas; private Group group; private GraphicsContext gc; + private List boatPolygons = new ArrayList<>(); - private final int MARK_SIZE = 10; - private final int BUFFER_SIZE = 25; - private final int CANVAS_SIZE = 1000; - private final int LHS_BUFFER = BUFFER_SIZE; - private final int RHS_BUFFER = BUFFER_SIZE + MARK_SIZE / 2; - private final int TOP_BUFFER = BUFFER_SIZE; - private final int BOT_BUFFER = TOP_BUFFER + MARK_SIZE / 2; + private final int MARK_SIZE = 10; + private final int BUFFER_SIZE = 25; + private final int CANVAS_WIDTH = 1000; + private final int CANVAS_HEIGHT = 1000; + private final int LHS_BUFFER = BUFFER_SIZE; + private final int RHS_BUFFER = BUFFER_SIZE + MARK_SIZE / 2; + private final int TOP_BUFFER = BUFFER_SIZE; + private final int BOT_BUFFER = TOP_BUFFER + MARK_SIZE / 2; + private final int FRAME_RATE = 60; private double distanceScaleFactor; private ScaleDirection scaleDirection; @@ -49,6 +55,9 @@ public class CanvasController { private Mark maxLonPoint; private int referencePointX; private int referencePointY; + private double metersToPixels; + + public AnimationTimer timer; private enum ScaleDirection { HORIZONTAL, @@ -67,70 +76,119 @@ public class CanvasController { canvasPane.getChildren().add(canvas); canvasPane.getChildren().add(group); // Bind canvas size to stack pane size. - canvas.widthProperty().bind(new SimpleDoubleProperty(CANVAS_SIZE)); - canvas.heightProperty().bind(new SimpleDoubleProperty(CANVAS_SIZE)); - group.minWidth(CANVAS_SIZE); - group.minHeight(CANVAS_SIZE); -// canvas.widthProperty().bind(canvasPane.widthProperty()); -// canvas.heightProperty().bind(canvasPane.heightProperty()); -// group.minWidth(canvas.getWidth()); -// group.minHeight(canvas.getHeight()); - - + canvas.widthProperty().bind(new SimpleDoubleProperty(CANVAS_WIDTH)); + canvas.heightProperty().bind(new SimpleDoubleProperty(CANVAS_HEIGHT)); + group.minWidth(CANVAS_WIDTH); + group.minHeight(CANVAS_HEIGHT); } - - public void setUpBoats(){ + public void initializeCanvas (){ gc = canvas.getGraphicsContext2D(); gc.save(); gc.setFill(Color.SKYBLUE); - gc.fillRect(0,0, CANVAS_SIZE,CANVAS_SIZE); + gc.fillRect(0,0, CANVAS_WIDTH, CANVAS_HEIGHT); gc.restore(); drawCourse(); - for (Mark m : raceViewController.getRace().getCourse()) - { - System.out.println("MARK NAME - " + m.getName()); - System.out.println("X LOCATION - " + m.getX()); - System.out.println("Y LOCATION - " + m.getY()); - } drawBoats(); - 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; +// 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()); +// +// +// // If race has started, draw the boats and play the timeline +// if (raceViewController.getRace().getRaceTime() > 1) { +// raceViewController.playTimelines(); +// } +// // Race has not started, pause the timelines +// else { +// raceViewController.pauseTimelines(); +// } +// lastUpdate = now; +// fpsCount ++; +// if (now - lastFpsUpdate >= 1000000000){ +// lastFpsCount = fpsCount; +// fpsCount = 0; +// lastFpsUpdate = now; +// } +// } +// } +// }; +// timer.start(); + //try { + // Thread.sleep(10000); + //}catch (Exception e) { + // e.printStackTrace(); + //} + timer = new AnimationTimer() { + + private int countdown = 60; + private int[] currentRaceMarker = {1, 1, 1, 1, 1, 1}; + List marks = raceViewController.getRace().getCourse(); @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()); - - - // If race has started, draw the boats and play the timeline - if (raceViewController.getRace().getRaceTime() > 1){ - raceViewController.playTimelines(); + boolean raceFinished = true; + boolean descending; + int boatIndex = 0; + Mark nextMark; + if (countdown == 0) { + for (BoatPolygon bp : boatPolygons) { + if (currentRaceMarker[boatIndex] < marks.size()) { + if (currentRaceMarker[boatIndex] == 6) { + int debugLine = 4; + } + double xb4 = bp.getLayoutX(); + double yb4 = bp.getLayoutY(); + nextMark = marks.get(currentRaceMarker[boatIndex]); + if (nextMark.getY() > bp.getLayoutY()) + descending = true; + else + descending = false; + bp.updatePosition(1000 / 60); + if (descending && nextMark.getY() < bp.getLayoutY()) { + currentRaceMarker[boatIndex]++; + bp.setDestination( + marks.get(currentRaceMarker[boatIndex]).getX(), marks.get(currentRaceMarker[boatIndex]).getY() + ); + } else if (!descending && nextMark.getY() > bp.getLayoutY()) { + currentRaceMarker[boatIndex]++; + bp.setDestination( + marks.get(currentRaceMarker[boatIndex]).getX(), marks.get(currentRaceMarker[boatIndex]).getY() + ); + } + double xnew = bp.getLayoutX(); + double ynew = bp.getLayoutY(); + double dx = xnew - xb4; + double dy = ynew -yb4; + raceFinished = false; + boatIndex++; + } } - // Race has not started, pause the timelines - else { - raceViewController.pauseTimelines(); - } - lastUpdate = now; - fpsCount ++; - if (now - lastFpsUpdate >= 1000000000){ - lastFpsCount = fpsCount; - fpsCount = 0; - lastFpsUpdate = now; + if (raceFinished) { + System.out.println("DONZEO LADS"); + this.stop(); } + } else { + countdown--; } } }; - timer.start(); + for (Mark m : raceViewController.getRace().getCourse()) + System.out.println(m.getName()); + //timer.start(); } class ResizableCanvas extends Canvas { @@ -179,16 +237,22 @@ public class CanvasController { */ private void drawBoats() { // Map timelineInfos = raceViewController.getTimelineInfos(); - ArrayList boats = raceViewController.getStartingBoats(); - Double startingX = (double) raceViewController.getRace().getCourse().get(0).getX(); - Double startingY = (double) raceViewController.getRace().getCourse().get(0).getY(); + List boats = raceViewController.getStartingBoats(); + List marks = raceViewController.getRace().getCourse(); + Double startingX = (double) marks.get(0).getX(); + Double startingY = (double) marks.get(0).getY(); + Double firstMarkX = (double) marks.get(1).getX(); + Double firstMarkY = (double) marks.get(1).getY(); 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()); + BoatPolygon bp = new BoatPolygon(boat, Colors.getColor()); + bp.moveBoatTo(startingX, startingY); + bp.setDestination(firstMarkX, firstMarkY); + group.getChildren().add(bp.getWake()); + group.getChildren().add(bp); + group.getChildren().add(bp.getTeamNameObject()); + group.getChildren().add(bp.getVelocityObject()); + boatPolygons.add(bp); // drawBoat(boat.getLongitude(), boat.getLatitude(), boat.getColor(), boat.getShortName(), boat.getSpeedInKnots(), boat.getHeading()); } } @@ -248,11 +312,11 @@ public class CanvasController { Color color = Color.BLUE; if (gateMark.getName().equals("Start")){ - color = Color.RED; + color = Color.GREEN; } if (gateMark.getName().equals("Finish")){ - color = Color.GREEN; + color = Color.RED; } drawSingleMark(gateMark.getSingleMark1(), color); @@ -282,14 +346,16 @@ public class CanvasController { double minLonToMaxLon = scaleRaceExtremities(); calculateReferencePointLocation(minLonToMaxLon); givePointsXY(); + findMetersToPixels(); } + /** * Sets the class variables minLatPoint, maxLatPoint, minLonPoint, maxLonPoint to the marker with the leftmost * marker, rightmost marker, southern most marker and northern most marker respectively. */ private void findMinMaxPoint() { - ArrayList sortedPoints = new ArrayList<>(); + List sortedPoints = new ArrayList<>(); for (Mark mark : raceViewController.getRace().getCourse()) { if (mark.getMarkType() == MarkType.SINGLE_MARK) @@ -348,9 +414,6 @@ public class CanvasController { } referencePoint.setX(referencePointX); referencePoint.setY(referencePointY); - System.out.println("REF POINT = " + referencePoint.getName()); - System.out.println(referencePointX); - System.out.println(referencePointY); } /** @@ -387,41 +450,44 @@ public class CanvasController { * are scaled according to the distanceScaleFactor variable. */ private void givePointsXY() { - Pair canvasLocation; - ArrayList allPoints = new ArrayList<>(raceViewController.getRace().getCourse()); + Point2D canvasLocation; + List allPoints = new ArrayList<>(raceViewController.getRace().getCourse()); for (Mark mark : allPoints) { if (mark.getMarkType() != MarkType.SINGLE_MARK) { GateMark gateMark = (GateMark) mark; canvasLocation = findScaledXY(gateMark.getSingleMark1()); - gateMark.getSingleMark1().setX(canvasLocation.getKey()); - gateMark.getSingleMark1().setY(canvasLocation.getValue()); + gateMark.getSingleMark1().setX((int) canvasLocation.getX()); + gateMark.getSingleMark1().setY((int) canvasLocation.getY()); canvasLocation = findScaledXY(gateMark.getSingleMark2()); - gateMark.getSingleMark2().setX(canvasLocation.getKey()); - gateMark.getSingleMark2().setY(canvasLocation.getValue()); + gateMark.getSingleMark2().setX((int) canvasLocation.getX()); + gateMark.getSingleMark2().setY((int) canvasLocation.getY()); } if (mark.getMarkType() == MarkType.CLOSED_GATE) ((GateMark) mark).assignXYCentered(); else { canvasLocation = findScaledXY(mark); - mark.setX(canvasLocation.getKey()); - mark.setY(canvasLocation.getValue()); + mark.setX((int) canvasLocation.getX()); + mark.setY((int) canvasLocation.getY()); } } } - private Pair findScaledXY (Mark unscaled) { + private Point2D findScaledXY (Mark unscaled) { + return findScaledXY (minLatPoint.getLatitude(), minLatPoint.getLongitude(), + unscaled.getLatitude(), unscaled.getLongitude()); + } + + private Point2D findScaledXY (double latA, double lonA, double latB, double lonB) { double distanceFromReference; double angleFromReference; int yAxisLocation; int xAxisLocation; - angleFromReference = Mark.calculateHeadingRad(minLatPoint, unscaled); - distanceFromReference = Mark.calculateDistance(minLatPoint, unscaled); - //angleFromReference = Mark.calculateHeadingRad(lon1, lon2, lat1, lat2); - //distanceFromReference = Mark.calculateDistance(lon1, lon2, lat1, lat2); + angleFromReference = Mark.calculateHeadingRad(latA, lonA, latB, lonB); + distanceFromReference = Mark.calculateDistance(latA, lonA, latB, lonB); if (angleFromReference > (Math.PI / 2)) { angleFromReference = (Math.PI * 2) - angleFromReference; @@ -434,6 +500,40 @@ public class CanvasController { yAxisLocation = referencePointY; yAxisLocation -= (int) Math.round(distanceScaleFactor * Math.cos(angleFromReference) * distanceFromReference); - return new Pair<>(xAxisLocation, yAxisLocation); + return new Point2D(xAxisLocation, yAxisLocation); + } + + + + /** + * Find the number of meters per pixel. + */ + private void findMetersToPixels () { + Double angularDistance; + Double angle; + Double straightLineDistance; + if (scaleDirection == ScaleDirection.HORIZONTAL) { + angularDistance = Mark.calculateDistance(minLonPoint, maxLonPoint); + angle = Mark.calculateHeadingRad(minLonPoint, maxLonPoint); + if (angle > Math.PI / 2) { + straightLineDistance = Math.cos(angle - Math.PI) * angularDistance; + } else { + straightLineDistance = Math.cos(angle) * angularDistance; + } + metersToPixels = (CANVAS_WIDTH - RHS_BUFFER - LHS_BUFFER) / straightLineDistance; + } else { + angularDistance = Mark.calculateDistance(minLatPoint, maxLatPoint); + angle = Mark.calculateHeadingRad(minLatPoint, maxLatPoint); + if (angle < Math.PI / 2) { + straightLineDistance = Math.cos(angle) * angularDistance; + } else { + straightLineDistance = Math.cos(-angle + Math.PI * 2) * angularDistance; + } + metersToPixels = (CANVAS_HEIGHT - TOP_BUFFER - BOT_BUFFER) / straightLineDistance; + } + } + + private Point2D latLonToXY (double latitude, double longitude) { + return findScaledXY(minLatPoint.getLatitude(), minLatPoint.getLongitude(), latitude, longitude); } } \ No newline at end of file diff --git a/src/main/java/seng302/controllers/RaceViewController.java b/src/main/java/seng302/controllers/RaceViewController.java index 83352357..8acfba2b 100644 --- a/src/main/java/seng302/controllers/RaceViewController.java +++ b/src/main/java/seng302/controllers/RaceViewController.java @@ -2,10 +2,7 @@ package seng302.controllers; import javafx.animation.Animation; import javafx.animation.KeyFrame; -import javafx.animation.KeyValue; import javafx.animation.Timeline; -import javafx.beans.property.DoubleProperty; -import javafx.beans.property.SimpleDoubleProperty; import javafx.beans.value.ChangeListener; import javafx.beans.value.ObservableValue; import javafx.fxml.FXML; @@ -65,15 +62,15 @@ public class RaceViewController { // } includedCanvasController.setup(this); - includedCanvasController.setUpBoats(); - initializeTimer(); + includedCanvasController.initializeCanvas(); + //initializeTimer(); initializeSettings(); //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); - + includedCanvasController.timer.start(); } private void initializeSettings(){ diff --git a/src/main/java/seng302/models/Boat.java b/src/main/java/seng302/models/Boat.java index f39f9e3b..004ddc57 100644 --- a/src/main/java/seng302/models/Boat.java +++ b/src/main/java/seng302/models/Boat.java @@ -5,42 +5,26 @@ import javafx.scene.shape.Polygon; import javafx.scene.text.Text; import javafx.scene.transform.Rotate; import javafx.scene.transform.Translate; +import javafx.util.Pair; /** * 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 - private double lon; // - - private double distanceToNextMark; - private Color color; - private int markLastPast; + private String teamName; + private double velocity; + private double lat; + private double lon; private double heading; + private int markLastPast; 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 this.lat = 0.0; this.lon = 0.0; - this.distanceToNextMark = 0.0; this.shortName = ""; } @@ -54,16 +38,7 @@ public class Boat { public Boat(String teamName, double boatVelocity, String shortName) { this.teamName = teamName; this.velocity = boatVelocity; - 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"); } /** @@ -113,8 +88,9 @@ public class Boat { this.lon = lon; } - public void setDistanceToNextMark(double distance){ - this.distanceToNextMark = distance; + public Pair getLocation () + { + return new Pair<>(this.lat, this.lon); } public double getLatitude(){ @@ -125,8 +101,12 @@ public class Boat { return this.lon; } - public Color getColor() { - return color; + public void setLatitude (double latitude) { + this.lat = latitude; + } + + public void setlongitude (double longitude) { + this.lon =longitude; } public double getSpeedInKnots(){ @@ -141,92 +121,16 @@ public class Boat { return markLastPast; } - 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; - } - public double getHeading(){ return this.heading; } + public void setHeading(double heading) { + this.heading = heading; + } + 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/BoatPolygon.java b/src/main/java/seng302/models/BoatPolygon.java new file mode 100644 index 00000000..f2e8ea43 --- /dev/null +++ b/src/main/java/seng302/models/BoatPolygon.java @@ -0,0 +1,171 @@ +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; + +/** + * Created by cir27 on 24/04/17. + */ +public class BoatPolygon extends Polygon { + + 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; + private static final double BOAT_HEIGHT = 15d; + private static final double BOAT_WIDTH = 10d; + //Time between sections of race - Should be changed to 200 for actual program. + private static double expectedUpdateInterval = 5000; + + private Boat boat; + private Polygon wake; + private Text teamNameObject; + private Text velocityObject; + + private double rotation; + private double pixelVelocityX; + private double pixelVelocityY; + //private double destinationX; + //private double destinationY; + + public BoatPolygon (Boat boat, Color color){ + super(); + super.setFill(color); + super.getPoints().addAll( + BOAT_WIDTH / 2, 0.0, + BOAT_WIDTH , BOAT_HEIGHT, + 0.0 , BOAT_HEIGHT + ); + this.boat = boat; + initAnnotations(); + } + + public BoatPolygon (Boat boat, Color color, double... points) + { + super(points); + super.setFill(color); + this.boat = boat; + initAnnotations(); + } + + private void initAnnotations () + { + wake = new Polygon(); + wake.setFill(Color.DARKBLUE); + wake.getPoints().addAll( + 5.0,0.0, + 10.0, boat.getVelocity() * VELOCITY_WAKE_RATIO, + 0.0, boat.getVelocity() * VELOCITY_WAKE_RATIO + ); + teamNameObject = new Text(boat.getShortName()); + velocityObject = new Text(String.valueOf(boat.getVelocity())); + } + /** + * Moves the boat and its children annotations from its current coordinates by specified amounts. + * @param dx The amount to move the X coordinate by + * @param dy The amount to move the Y coordinate by + */ + void moveBy(Double dx, Double dy) { + super.setLayoutX(super.getLayoutX() + dx); + super.setLayoutY(super.getLayoutY() + dy); + super.relocate(super.getLayoutX(), super.getLayoutY()); + + teamNameObject.setX(teamNameObject.getX() + dx); + teamNameObject.setY(teamNameObject.getY() + dy); + teamNameObject.relocate(teamNameObject.getX(), teamNameObject.getY()); + + velocityObject.setX(velocityObject.getX() + dx); + velocityObject.setY(velocityObject.getY() + dy); + velocityObject.relocate(velocityObject.getX(), velocityObject.getY()); + + wake.setLayoutX(wake.getLayoutX() + dx); + wake.setLayoutY(wake.getLayoutY() + dy); + 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) { + super.setLayoutX(x); + super.setLayoutY(y); + super.relocate(super.getLayoutX(), super.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()); + } + + public void updatePosition (double timeInterval) { + double dx = pixelVelocityX * timeInterval; + double dy = pixelVelocityY * timeInterval; + moveBy(dx, dy); + } + + public void setDestination (double newXValue, double newYValue) { + this.pixelVelocityX = (newXValue - super.getLayoutX()) / expectedUpdateInterval; + this.pixelVelocityY = (newYValue - super.getLayoutY()) / expectedUpdateInterval; + //this.destinationX = newXValue; + //this.destinationY = newYValue; + this.rotation = Math.abs( + Math.toDegrees( + Math.atan( + (newYValue - super.getLayoutY()) / (newXValue - super.getLayoutX()) + ) + ) + ); + if (super.getLayoutY() >= newYValue && super.getLayoutX() <= newXValue) + rotation = 90 - rotation; + else if (super.getLayoutY() < newYValue && super.getLayoutX() <= newXValue) + rotation = 90 + rotation; + else if (super.getLayoutY() >= newYValue && super.getLayoutX() > newXValue) + rotation = 270 + rotation; + else + rotation = 270 - rotation; + rotateBoat (); + } + + private void rotateBoat () { + super.getTransforms().clear(); + super.getTransforms().add(new Rotate(rotation, BOAT_WIDTH/2, 0)); + wake.getTransforms().clear(); + wake.getTransforms().add(new Translate(0, BOAT_HEIGHT)); + wake.getTransforms().add(new Rotate(rotation, BOAT_WIDTH/2, -BOAT_HEIGHT)); + } + + public static double getExpectedUpdateInterval() { + return expectedUpdateInterval; + } + + public static void setExpectedUpdateInterval(double expectedUpdateInterval) { + BoatPolygon.expectedUpdateInterval = expectedUpdateInterval; + } + + public Polygon getWake() { + return wake; + } + + public Text getTeamNameObject() { + return teamNameObject; + } + + public Text getVelocityObject() { + return velocityObject; + } + +} diff --git a/src/main/java/seng302/models/Colors.java b/src/main/java/seng302/models/Colors.java index 419753dc..23ef8f4e 100644 --- a/src/main/java/seng302/models/Colors.java +++ b/src/main/java/seng302/models/Colors.java @@ -11,10 +11,9 @@ public enum Colors { static Integer index = 0; public static Color getColor() { - index++; - if (index > 6) { - index = 1; + if (index == 6) { + index = 0; } - return Color.valueOf(values()[index-1].toString()); + return Color.valueOf(values()[index++].toString()); } } diff --git a/src/main/java/seng302/models/Race.java b/src/main/java/seng302/models/Race.java index 165d9468..01d1adf8 100644 --- a/src/main/java/seng302/models/Race.java +++ b/src/main/java/seng302/models/Race.java @@ -9,6 +9,7 @@ import java.util.*; * Created by mra106 on 8/3/2017. */ public class Race { + private ArrayList boats; // The boats in the race private ArrayList finishingOrder; // The order in which the boats finish the race private HashMap events = new HashMap<>(); // The events that occur in the race diff --git a/src/main/java/seng302/models/mark/Mark.java b/src/main/java/seng302/models/mark/Mark.java index 44e9877b..98af4057 100644 --- a/src/main/java/seng302/models/mark/Mark.java +++ b/src/main/java/seng302/models/mark/Mark.java @@ -42,7 +42,7 @@ public abstract class Mark { Double longitude2 = pointTwo.getLongitude(); Double latitude1 = pointOne.getLatitude(); Double latitude2 = pointTwo.getLatitude(); - return calculateHeadingRad(longitude1, longitude2, latitude1, latitude2); + return calculateHeadingRad(latitude1, longitude1, latitude2, longitude2); } /** @@ -54,7 +54,7 @@ public abstract class Mark { * @param latitude2 Latitude of first point in degrees * @return Heading in radians */ - public static double calculateHeadingRad (Double longitude1, Double longitude2, Double latitude1, Double latitude2) { + public static double calculateHeadingRad (Double latitude1, Double longitude1, Double latitude2, Double longitude2) { latitude1 = Math.toRadians(latitude1); latitude2 = Math.toRadians(latitude2); Double longDiff= Math.toRadians(longitude2-longitude1); @@ -75,7 +75,7 @@ public abstract class Mark { Double longitude2 = pointTwo.getLongitude(); Double latitude1 = pointOne.getLatitude(); Double latitude2 = pointTwo.getLatitude(); - return calculateDistance(longitude1, longitude2, latitude1, latitude2); + return calculateDistance(latitude1, longitude1, latitude2, longitude2); } /** @@ -88,7 +88,7 @@ public abstract class Mark { * @param latitude2 Latitude of first point in degrees * @return Distance in meters */ - public static Double calculateDistance (Double longitude1, Double longitude2, Double latitude1, Double latitude2) { + public static Double calculateDistance (Double latitude1, Double longitude1, Double latitude2, Double longitude2) { Double theta = longitude1 - longitude2; Double dist = Math.sin(Math.toRadians(latitude1)) * Math.sin(Math.toRadians(latitude2)) + Math.cos(Math.toRadians(latitude1)) * Math.cos(Math.toRadians(latitude2)) * diff --git a/src/test/java/seng302/ColorsTest.java b/src/test/java/seng302/ColorsTest.java index a6681b26..84fc01dd 100644 --- a/src/test/java/seng302/ColorsTest.java +++ b/src/test/java/seng302/ColorsTest.java @@ -1,8 +1,11 @@ package seng302; import javafx.scene.paint.Color; +import javafx.scene.paint.Paint; +import org.junit.Assert; import org.junit.Test; import seng302.models.Boat; +import seng302.models.BoatPolygon; import seng302.models.Colors; import java.util.ArrayList; @@ -16,30 +19,13 @@ import static org.junit.Assert.assertEquals; * Created by ryan_ on 16/03/2017. */ public class ColorsTest { - //@Test + + @Test public void testNextColor() { - List boats = new ArrayList<>(); - boats.add(new Boat("Team 1")); - boats.add(new Boat("Team 2")); - boats.add(new Boat("Team 3")); - boats.add(new Boat("Team 4")); - boats.add(new Boat("Team 5")); - boats.add(new Boat("Team 6")); - - int count = 0; - List enumColors = new ArrayList<>(); - while (count < 6) { - Color color = Colors.getColor(); - enumColors.add(color); - count++; + Color expectedColors[] = {Color.RED, Color.ORANGE, Color.YELLOW, Color.GREEN, Color.BLUE, Color.PURPLE}; + for (int i = 0; i<6; i++) + { + Assert.assertEquals(expectedColors[i], Colors.getColor()); } - - List boatColors = new ArrayList<>(); - for (Boat boat : boats) { - Color color = boat.getColor(); - boatColors.add(color); - } - - assertEquals(enumColors, boatColors); } } From ef874b42455138c944e1d786d324e5e72a36bb0d Mon Sep 17 00:00:00 2001 From: cir27 Date: Tue, 25 Apr 2017 03:12:30 +1200 Subject: [PATCH 4/5] Added a transition time to rotational movement. The aim is to make animations smoother when the boat turns. Unsure if current implementation will look good without testing on a datastream. #story30c --- .../seng302/controllers/CanvasController.java | 3 +- src/main/java/seng302/models/BoatPolygon.java | 48 ++++++++++++++----- 2 files changed, 37 insertions(+), 14 deletions(-) diff --git a/src/main/java/seng302/controllers/CanvasController.java b/src/main/java/seng302/controllers/CanvasController.java index 03db02f1..01623b5b 100644 --- a/src/main/java/seng302/controllers/CanvasController.java +++ b/src/main/java/seng302/controllers/CanvasController.java @@ -246,8 +246,9 @@ public class CanvasController { for (Boat boat : boats) { BoatPolygon bp = new BoatPolygon(boat, Colors.getColor()); - bp.moveBoatTo(startingX, startingY); + bp.moveBoatTo(startingX, startingY, 0d); bp.setDestination(firstMarkX, firstMarkY); + bp.forceRotation(); group.getChildren().add(bp.getWake()); group.getChildren().add(bp); group.getChildren().add(bp.getTeamNameObject()); diff --git a/src/main/java/seng302/models/BoatPolygon.java b/src/main/java/seng302/models/BoatPolygon.java index f2e8ea43..aba75ed5 100644 --- a/src/main/java/seng302/models/BoatPolygon.java +++ b/src/main/java/seng302/models/BoatPolygon.java @@ -1,6 +1,7 @@ package seng302.models; +import com.sun.xml.internal.bind.v2.TODO; import javafx.scene.paint.Color; import javafx.scene.shape.Polygon; import javafx.scene.text.Text; @@ -27,7 +28,9 @@ public class BoatPolygon extends Polygon { private Text teamNameObject; private Text velocityObject; - private double rotation; + private double rotationalGoal; + private double currentRotation; + private double rotationalVelocity; private double pixelVelocityX; private double pixelVelocityY; //private double destinationX; @@ -70,7 +73,7 @@ public class BoatPolygon extends Polygon { * @param dx The amount to move the X coordinate by * @param dy The amount to move the Y coordinate by */ - void moveBy(Double dx, Double dy) { + void moveBy(Double dx, Double dy, Double rotation) { super.setLayoutX(super.getLayoutX() + dx); super.setLayoutY(super.getLayoutY() + dy); super.relocate(super.getLayoutX(), super.getLayoutY()); @@ -86,6 +89,7 @@ public class BoatPolygon extends Polygon { wake.setLayoutX(wake.getLayoutX() + dx); wake.setLayoutY(wake.getLayoutY() + dy); wake.relocate(wake.getLayoutX(), wake.getLayoutY()); + rotateBoat(rotation); } /** @@ -93,7 +97,7 @@ public class BoatPolygon extends Polygon { * @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) { + public void moveBoatTo(Double x, Double y, Double rotation) { super.setLayoutX(x); super.setLayoutY(y); super.relocate(super.getLayoutX(), super.getLayoutY()); @@ -109,12 +113,20 @@ public class BoatPolygon extends Polygon { wake.setLayoutX(x); wake.setLayoutY(y); wake.relocate(wake.getLayoutX(), wake.getLayoutY()); + currentRotation = 0; + rotateBoat(rotation); } public void updatePosition (double timeInterval) { double dx = pixelVelocityX * timeInterval; double dy = pixelVelocityY * timeInterval; - moveBy(dx, dy); + double rotation = 0d; + if (rotationalGoal > currentRotation && rotationalVelocity > 0) { + rotation = rotationalVelocity * timeInterval; + } else if (rotationalGoal < currentRotation && rotationalVelocity < 0) { + rotation = rotationalVelocity * timeInterval; + } + moveBy(dx, dy, rotation); } public void setDestination (double newXValue, double newYValue) { @@ -122,7 +134,7 @@ public class BoatPolygon extends Polygon { this.pixelVelocityY = (newYValue - super.getLayoutY()) / expectedUpdateInterval; //this.destinationX = newXValue; //this.destinationY = newYValue; - this.rotation = Math.abs( + this.rotationalGoal = Math.abs( Math.toDegrees( Math.atan( (newYValue - super.getLayoutY()) / (newXValue - super.getLayoutX()) @@ -130,22 +142,29 @@ public class BoatPolygon extends Polygon { ) ); if (super.getLayoutY() >= newYValue && super.getLayoutX() <= newXValue) - rotation = 90 - rotation; + rotationalGoal = 90 - rotationalGoal; else if (super.getLayoutY() < newYValue && super.getLayoutX() <= newXValue) - rotation = 90 + rotation; + rotationalGoal = 90 + rotationalGoal; else if (super.getLayoutY() >= newYValue && super.getLayoutX() > newXValue) - rotation = 270 + rotation; + rotationalGoal = 270 + rotationalGoal; else - rotation = 270 - rotation; - rotateBoat (); + rotationalGoal = 270 - rotationalGoal; + // TODO: 25/04/2017 cir27 - Verify this logic is correct. Want to produce the shortest path. + if (Math.abs(360 - rotationalGoal + currentRotation) < Math.abs(rotationalGoal - currentRotation)) { + System.out.println("ROTATE"); + this.rotationalVelocity = (360 - rotationalGoal + currentRotation) / expectedUpdateInterval; + } else { + this.rotationalVelocity = (rotationalGoal - currentRotation) / expectedUpdateInterval; + } } - private void rotateBoat () { + public void rotateBoat (double rotationDeg) { + currentRotation += rotationDeg; super.getTransforms().clear(); - super.getTransforms().add(new Rotate(rotation, BOAT_WIDTH/2, 0)); + super.getTransforms().add(new Rotate(currentRotation, BOAT_WIDTH/2, 0)); wake.getTransforms().clear(); wake.getTransforms().add(new Translate(0, BOAT_HEIGHT)); - wake.getTransforms().add(new Rotate(rotation, BOAT_WIDTH/2, -BOAT_HEIGHT)); + wake.getTransforms().add(new Rotate(currentRotation, BOAT_WIDTH/2, -BOAT_HEIGHT)); } public static double getExpectedUpdateInterval() { @@ -168,4 +187,7 @@ public class BoatPolygon extends Polygon { return velocityObject; } + public void forceRotation () { + rotateBoat (rotationalGoal - currentRotation); + } } From 42569e6ad749c8bcc32097ed630ec2b4ac3049db Mon Sep 17 00:00:00 2001 From: cir27 Date: Tue, 25 Apr 2017 04:30:44 +1200 Subject: [PATCH 5/5] Changed BoatPolygon is now a group instead of a polygon and is called BoatGroup. BoatPolygon's functionality was more maintainable and scalable by having it extend Group. #story30c --- .../seng302/controllers/CanvasController.java | 59 ++++---- .../{BoatPolygon.java => BoatGroup.java} | 130 ++++++++---------- src/test/java/seng302/ColorsTest.java | 7 - 3 files changed, 90 insertions(+), 106 deletions(-) rename src/main/java/seng302/models/{BoatPolygon.java => BoatGroup.java} (66%) diff --git a/src/main/java/seng302/controllers/CanvasController.java b/src/main/java/seng302/controllers/CanvasController.java index 01623b5b..a679e52f 100644 --- a/src/main/java/seng302/controllers/CanvasController.java +++ b/src/main/java/seng302/controllers/CanvasController.java @@ -12,7 +12,7 @@ import javafx.scene.paint.Color; import javafx.scene.text.Font; import javafx.util.Pair; import seng302.models.Boat; -import seng302.models.BoatPolygon; +import seng302.models.BoatGroup; import seng302.models.Colors; import seng302.models.mark.GateMark; import seng302.models.mark.Mark; @@ -35,7 +35,7 @@ public class CanvasController { private ResizableCanvas canvas; private Group group; private GraphicsContext gc; - private List boatPolygons = new ArrayList<>(); + private List boatGroups = new ArrayList<>(); private final int MARK_SIZE = 10; private final int BUFFER_SIZE = 25; @@ -142,35 +142,47 @@ public class CanvasController { public void handle(long now) { boolean raceFinished = true; boolean descending; + boolean leftToRight; int boatIndex = 0; Mark nextMark; if (countdown == 0) { - for (BoatPolygon bp : boatPolygons) { + for (BoatGroup boatGroup : boatGroups) { if (currentRaceMarker[boatIndex] < marks.size()) { if (currentRaceMarker[boatIndex] == 6) { int debugLine = 4; } - double xb4 = bp.getLayoutX(); - double yb4 = bp.getLayoutY(); + double xb4 = boatGroup.getLayoutX(); + double yb4 = boatGroup.getLayoutY(); nextMark = marks.get(currentRaceMarker[boatIndex]); - if (nextMark.getY() > bp.getLayoutY()) - descending = true; - else - descending = false; - bp.updatePosition(1000 / 60); - if (descending && nextMark.getY() < bp.getLayoutY()) { + + descending = nextMark.getY() > boatGroup.getLayoutY(); + leftToRight = nextMark.getX() < boatGroup.getLayoutX(); + + boatGroup.updatePosition(1000 / 60); + if (descending && nextMark.getY() < boatGroup.getLayoutY()) { currentRaceMarker[boatIndex]++; - bp.setDestination( + boatGroup.setDestination( marks.get(currentRaceMarker[boatIndex]).getX(), marks.get(currentRaceMarker[boatIndex]).getY() ); - } else if (!descending && nextMark.getY() > bp.getLayoutY()) { + } else if (!descending && nextMark.getY() > boatGroup.getLayoutY()) { currentRaceMarker[boatIndex]++; - bp.setDestination( + boatGroup.setDestination( + marks.get(currentRaceMarker[boatIndex]).getX(), marks.get(currentRaceMarker[boatIndex]).getY() + ); + } else if (leftToRight && nextMark.getX() > boatGroup.getLayoutX()) { + currentRaceMarker[boatIndex]++; + boatGroup.setDestination( + marks.get(currentRaceMarker[boatIndex]).getX(), marks.get(currentRaceMarker[boatIndex]).getY() + ); + } else if (!leftToRight && nextMark.getX() < boatGroup.getLayoutX()) { + currentRaceMarker[boatIndex]++; + boatGroup.setDestination( marks.get(currentRaceMarker[boatIndex]).getX(), marks.get(currentRaceMarker[boatIndex]).getY() ); } - double xnew = bp.getLayoutX(); - double ynew = bp.getLayoutY(); + + double xnew = boatGroup.getLayoutX(); + double ynew = boatGroup.getLayoutY(); double dx = xnew - xb4; double dy = ynew -yb4; raceFinished = false; @@ -245,15 +257,12 @@ public class CanvasController { Double firstMarkY = (double) marks.get(1).getY(); for (Boat boat : boats) { - BoatPolygon bp = new BoatPolygon(boat, Colors.getColor()); - bp.moveBoatTo(startingX, startingY, 0d); - bp.setDestination(firstMarkX, firstMarkY); - bp.forceRotation(); - group.getChildren().add(bp.getWake()); - group.getChildren().add(bp); - group.getChildren().add(bp.getTeamNameObject()); - group.getChildren().add(bp.getVelocityObject()); - boatPolygons.add(bp); + BoatGroup boatGroup = new BoatGroup(boat, Colors.getColor()); + boatGroup.moveBoatTo(startingX, startingY, 0d); + boatGroup.setDestination(firstMarkX, firstMarkY); + boatGroup.forceRotation(); + group.getChildren().add(boatGroup); + boatGroups.add(boatGroup); // drawBoat(boat.getLongitude(), boat.getLatitude(), boat.getColor(), boat.getShortName(), boat.getSpeedInKnots(), boat.getHeading()); } } diff --git a/src/main/java/seng302/models/BoatPolygon.java b/src/main/java/seng302/models/BoatGroup.java similarity index 66% rename from src/main/java/seng302/models/BoatPolygon.java rename to src/main/java/seng302/models/BoatGroup.java index aba75ed5..06ea1306 100644 --- a/src/main/java/seng302/models/BoatPolygon.java +++ b/src/main/java/seng302/models/BoatGroup.java @@ -1,7 +1,7 @@ package seng302.models; - -import com.sun.xml.internal.bind.v2.TODO; +import javafx.scene.Group; +import javafx.scene.Node; import javafx.scene.paint.Color; import javafx.scene.shape.Polygon; import javafx.scene.text.Text; @@ -9,10 +9,9 @@ import javafx.scene.transform.Rotate; import javafx.scene.transform.Translate; /** - * Created by cir27 on 24/04/17. + * Created by CJIRWIN on 25/04/2017. */ -public class BoatPolygon extends Polygon { - +public class BoatGroup extends Group{ private static final double TEAMNAME_X_OFFSET = 15d; private static final double TEAMNAME_Y_OFFSET = -20d; private static final double VELOCITY_X_OFFSET = 15d; @@ -21,52 +20,66 @@ public class BoatPolygon extends Polygon { private static final double BOAT_HEIGHT = 15d; private static final double BOAT_WIDTH = 10d; //Time between sections of race - Should be changed to 200 for actual program. - private static double expectedUpdateInterval = 5000; + private static double expectedUpdateInterval = 2000; private Boat boat; - private Polygon wake; - private Text teamNameObject; - private Text velocityObject; private double rotationalGoal; private double currentRotation; private double rotationalVelocity; private double pixelVelocityX; private double pixelVelocityY; - //private double destinationX; - //private double destinationY; - public BoatPolygon (Boat boat, Color color){ + public BoatGroup (Boat boat, Color color){ super(); - super.setFill(color); - super.getPoints().addAll( - BOAT_WIDTH / 2, 0.0, - BOAT_WIDTH , BOAT_HEIGHT, - 0.0 , BOAT_HEIGHT - ); this.boat = boat; - initAnnotations(); + initChildren(color); } - public BoatPolygon (Boat boat, Color color, double... points) + public BoatGroup (Boat boat, Color color, double... points) { - super(points); - super.setFill(color); - this.boat = boat; - initAnnotations(); + super(); + initChildren(color, points); } - private void initAnnotations () - { - wake = new Polygon(); - wake.setFill(Color.DARKBLUE); - wake.getPoints().addAll( + private void initChildren (Color color, double... points) { + Polygon boatPoly = new Polygon(points); + boatPoly.setFill(color); + + Polygon wake = new Polygon( 5.0,0.0, 10.0, boat.getVelocity() * VELOCITY_WAKE_RATIO, 0.0, boat.getVelocity() * VELOCITY_WAKE_RATIO ); - teamNameObject = new Text(boat.getShortName()); - velocityObject = new Text(String.valueOf(boat.getVelocity())); + wake.setFill(Color.DARKBLUE); + + Text teamNameObject = new Text(boat.getShortName()); + Text velocityObject = new Text(String.valueOf(boat.getVelocity())); + + boatPoly.setLayoutX(0); + boatPoly.setLayoutY(0); + boatPoly.relocate(boatPoly.getLayoutX(), boatPoly.getLayoutY()); + + teamNameObject.setX(TEAMNAME_X_OFFSET); + teamNameObject.setY(TEAMNAME_Y_OFFSET); + teamNameObject.relocate(teamNameObject.getX(), teamNameObject.getY()); + + velocityObject.setX(VELOCITY_X_OFFSET); + velocityObject.setY(VELOCITY_Y_OFFSET); + velocityObject.relocate(velocityObject.getX(), velocityObject.getY()); + + wake.setLayoutX(0); + wake.setLayoutY(0); + wake.relocate(wake.getLayoutX(), wake.getLayoutY()); + + super.getChildren().addAll(boatPoly, wake, teamNameObject, velocityObject); + } + + private void initChildren (Color color) { + initChildren(color, + BOAT_WIDTH / 2, 0.0, + BOAT_WIDTH, BOAT_HEIGHT, + 0.0, BOAT_HEIGHT); } /** * Moves the boat and its children annotations from its current coordinates by specified amounts. @@ -76,19 +89,6 @@ public class BoatPolygon extends Polygon { void moveBy(Double dx, Double dy, Double rotation) { super.setLayoutX(super.getLayoutX() + dx); super.setLayoutY(super.getLayoutY() + dy); - super.relocate(super.getLayoutX(), super.getLayoutY()); - - teamNameObject.setX(teamNameObject.getX() + dx); - teamNameObject.setY(teamNameObject.getY() + dy); - teamNameObject.relocate(teamNameObject.getX(), teamNameObject.getY()); - - velocityObject.setX(velocityObject.getX() + dx); - velocityObject.setY(velocityObject.getY() + dy); - velocityObject.relocate(velocityObject.getX(), velocityObject.getY()); - - wake.setLayoutX(wake.getLayoutX() + dx); - wake.setLayoutY(wake.getLayoutY() + dy); - wake.relocate(wake.getLayoutX(), wake.getLayoutY()); rotateBoat(rotation); } @@ -98,21 +98,7 @@ public class BoatPolygon extends Polygon { * @param y The Y coordinate to move the boat to */ public void moveBoatTo(Double x, Double y, Double rotation) { - super.setLayoutX(x); - super.setLayoutY(y); - super.relocate(super.getLayoutX(), super.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()); + super.relocate(x, y); currentRotation = 0; rotateBoat(rotation); } @@ -160,8 +146,10 @@ public class BoatPolygon extends Polygon { public void rotateBoat (double rotationDeg) { currentRotation += rotationDeg; - super.getTransforms().clear(); - super.getTransforms().add(new Rotate(currentRotation, BOAT_WIDTH/2, 0)); + Node boatPoly = super.getChildren().get(0); + boatPoly.getTransforms().clear(); + boatPoly.getTransforms().add(new Rotate(currentRotation, BOAT_WIDTH/2, 0)); + Node wake = super.getChildren().get(1); wake.getTransforms().clear(); wake.getTransforms().add(new Translate(0, BOAT_HEIGHT)); wake.getTransforms().add(new Rotate(currentRotation, BOAT_WIDTH/2, -BOAT_HEIGHT)); @@ -172,22 +160,16 @@ public class BoatPolygon extends Polygon { } public static void setExpectedUpdateInterval(double expectedUpdateInterval) { - BoatPolygon.expectedUpdateInterval = expectedUpdateInterval; - } - - public Polygon getWake() { - return wake; - } - - public Text getTeamNameObject() { - return teamNameObject; - } - - public Text getVelocityObject() { - return velocityObject; + BoatGroup.expectedUpdateInterval = expectedUpdateInterval; } public void forceRotation () { rotateBoat (rotationalGoal - currentRotation); } + + public void toogleAnnotations () { + super.getChildren().get(1).setVisible(false); + super.getChildren().get(2).setVisible(false); + super.getChildren().get(3).setVisible(false); + } } diff --git a/src/test/java/seng302/ColorsTest.java b/src/test/java/seng302/ColorsTest.java index 84fc01dd..6fc07b41 100644 --- a/src/test/java/seng302/ColorsTest.java +++ b/src/test/java/seng302/ColorsTest.java @@ -1,17 +1,10 @@ package seng302; import javafx.scene.paint.Color; -import javafx.scene.paint.Paint; import org.junit.Assert; import org.junit.Test; -import seng302.models.Boat; -import seng302.models.BoatPolygon; import seng302.models.Colors; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals;