diff --git a/src/main/java/seng302/controllers/CanvasController.java b/src/main/java/seng302/controllers/CanvasController.java index 37ef3193..8934a471 100644 --- a/src/main/java/seng302/controllers/CanvasController.java +++ b/src/main/java/seng302/controllers/CanvasController.java @@ -11,6 +11,8 @@ import javafx.scene.canvas.Canvas; import javafx.scene.canvas.GraphicsContext; import javafx.scene.layout.AnchorPane; import javafx.scene.paint.Color; +import javafx.scene.shape.Arc; +import javafx.scene.shape.ArcType; import javafx.scene.text.Font; import javafx.util.Pair; import seng302.models.Boat; @@ -22,6 +24,7 @@ import seng302.models.parsers.StreamParser; import seng302.models.parsers.StreamReceiver; import java.sql.Time; +import java.text.DecimalFormat; import java.util.*; /** @@ -59,6 +62,13 @@ public class CanvasController { private double metersToPixels; private List raceObjects = new ArrayList<>(); + //FRAME RATE + private static final double UPDATE_TIME = 0.016666; // 1 / 60 ie 60fps + private final long[] frameTimes = new long[30]; + private int frameTimeIndex = 0; + private boolean arrayFilled = false; + private DecimalFormat decimalFormat2dp = new DecimalFormat("0.00"); + public AnimationTimer timer; private enum ScaleDirection { @@ -80,8 +90,8 @@ public class CanvasController { // Bind canvas size to stack pane size. canvas.widthProperty().bind(new SimpleDoubleProperty(CANVAS_WIDTH)); canvas.heightProperty().bind(new SimpleDoubleProperty(CANVAS_HEIGHT)); - group.minWidth(CANVAS_WIDTH); - group.minHeight(CANVAS_HEIGHT); + //group.minWidth(CANVAS_WIDTH); + //group.minHeight(CANVAS_HEIGHT); } public void initializeCanvas (){ @@ -138,6 +148,8 @@ public class CanvasController { timer = new AnimationTimer() { + + private int countdown = 60; private int[] currentRaceMarker = {1, 1, 1, 1, 1, 1}; List marks = raceViewController.getRace().getCourse(); @@ -150,6 +162,20 @@ public class CanvasController { int boatIndex = 0; Mark nextMark; + + long oldFrameTime = frameTimes[frameTimeIndex] ; + frameTimes[frameTimeIndex] = now ; + frameTimeIndex = (frameTimeIndex + 1) % frameTimes.length ; + if (frameTimeIndex == 0) { + arrayFilled = true ; + } + if (arrayFilled) { + long elapsedNanos = now - oldFrameTime ; + long elapsedNanosPerFrame = elapsedNanos / frameTimes.length ; + Double frameRate = 1_000_000_000.0 / elapsedNanosPerFrame ; + drawFps(frameRate.intValue()); + } + //if (countdown == 0) { //System.out.println("called the at"); for (RaceObject raceObject : raceObjects) { @@ -163,13 +189,10 @@ public class CanvasController { //descending = nextMark.getY() > boatGroup.getLayoutY(); //leftToRight = nextMark.getX() < boatGroup.getLayoutX(); - - raceObject.updatePosition(1000 / 60); for (int id : raceObject.getRaceIds()) { //System.out.println("id = " + id); if (id != 0 && StreamParser.boatPositions.size() > 0) { - boolean test = StreamParser.boatPositions.containsKey(id); if (StreamParser.boatPositions.containsKey((long) id)) { Point3D p = StreamParser.boatPositions.get((long) id); Point2D p2d = latLonToXY(p.getX(), p.getY()); @@ -285,10 +308,15 @@ public class CanvasController { private void drawFps(int fps){ if (raceViewController.isDisplayFps()){ + gc.clearRect(5,5,50,20); gc.setFill(Color.BLACK); gc.setFont(new Font(14)); gc.setLineWidth(3); gc.fillText(fps + " FPS", 5, 20); + } else { + gc.clearRect(5,5,50,20); + gc.setFill(Color.SKYBLUE); + gc.fillRect(4,4,51,21); } } @@ -303,16 +331,28 @@ public class CanvasController { Double startingY = raceObjects.get(0).getLayoutY(); Double firstMarkX = raceObjects.get(1).getLayoutX(); Double firstMarkY = raceObjects.get(1).getLayoutY(); + Arc a = new Arc(300, 300, 45, 45, -90, 45); + a.setType(ArcType.ROUND); + group.getChildren().add(a); + a = new Arc(500, 500, 45, 45, 450, 45); + a.setType(ArcType.ROUND); + group.getChildren().add(a); + + Group boatAnnotations = new Group(); for (Boat boat : boats) { BoatGroup boatGroup = new BoatGroup(boat, Colors.getColor()); + System.out.println("MADE A BOAT GROUP FOR " + boatGroup.getBoat().getShortName()); boatGroup.moveTo(startingX, startingY, 0d); - boatGroup.setDestination(firstMarkX, firstMarkY); +// boatGroup.setDestination(firstMarkX, firstMarkY); boatGroup.forceRotation(); - group.getChildren().add(boatGroup); + //group.getChildren().add(boatGroup); raceObjects.add(boatGroup); + boatAnnotations.getChildren().add(boatGroup.getLowPriorityAnnotations()); // drawBoat(boat.getLongitude(), boat.getLatitude(), boat.getColor(), boat.getShortName(), boat.getSpeedInKnots(), boat.getHeading()); } + group.getChildren().add(boatAnnotations); + group.getChildren().addAll(raceObjects); } @@ -555,14 +595,15 @@ public class CanvasController { // gateMark.getSingleMark2().setY((int) canvasLocation.getY()); markGroup = new MarkGroup(mark, findScaledXY(gateMark.getSingleMark1()), findScaledXY(gateMark.getSingleMark2())); - group.getChildren().add(markGroup); + //group.getChildren().add(markGroup); raceObjects.add(markGroup); } else { // canvasLocation = findScaledXY(mark); // mark.setX((int) canvasLocation.getX()); // mark.setY((int) canvasLocation.getY()); markGroup = new MarkGroup(mark, findScaledXY(mark)); - group.getChildren().add(markGroup); + raceObjects.add(markGroup); + //group.getChildren().add(markGroup); } processed.add(mark); } diff --git a/src/main/java/seng302/controllers/RaceViewController.java b/src/main/java/seng302/controllers/RaceViewController.java index e781c8d3..214cf02f 100644 --- a/src/main/java/seng302/controllers/RaceViewController.java +++ b/src/main/java/seng302/controllers/RaceViewController.java @@ -5,6 +5,7 @@ import javafx.animation.KeyFrame; import javafx.animation.Timeline; import javafx.beans.value.ChangeListener; import javafx.beans.value.ObservableValue; +import javafx.event.ActionEvent; import javafx.fxml.FXML; import javafx.fxml.FXMLLoader; import javafx.scene.control.CheckBox; diff --git a/src/main/java/seng302/models/Boat.java b/src/main/java/seng302/models/Boat.java index d275091f..04ef14ab 100644 --- a/src/main/java/seng302/models/Boat.java +++ b/src/main/java/seng302/models/Boat.java @@ -140,4 +140,5 @@ public class Boat { public int getId() { return id; } + } \ No newline at end of file diff --git a/src/main/java/seng302/models/BoatGroup.java b/src/main/java/seng302/models/BoatGroup.java index dd28b6b5..d7f023b1 100644 --- a/src/main/java/seng302/models/BoatGroup.java +++ b/src/main/java/seng302/models/BoatGroup.java @@ -2,16 +2,12 @@ package seng302.models; import javafx.geometry.Point2D; import javafx.scene.Group; -import javafx.scene.Node; import javafx.scene.paint.Color; import javafx.scene.shape.Line; import javafx.scene.shape.Polygon; import javafx.scene.text.Text; import javafx.scene.transform.Rotate; -import javafx.scene.transform.Translate; - -import java.util.ArrayList; -import java.util.List; +import seng302.models.parsers.StreamParser; /** * Created by CJIRWIN on 25/04/2017. @@ -25,23 +21,23 @@ public class BoatGroup extends RaceObject{ private static final double VELOCITY_WAKE_RATIO = 2d; private static final double BOAT_HEIGHT = 15d; private static final double BOAT_WIDTH = 10d; - private static final int LINE_INTERVAL = 120; - //Time between sections of race - Should be changed to 200 for actual program. + private static final int LINE_INTERVAL = 180; private static double expectedUpdateInterval = 200; private static int WAKE_FRAME_INTERVAL = 30; - private double framesForNewLine = LINE_INTERVAL; + private double framesForNewLine = 0; + private boolean destinationSet; private Point2D lastPoint; + private int wakeGenerationDelay; private Boat boat; private int wakeCounter = WAKE_FRAME_INTERVAL; - private List wakes = new ArrayList<>(); - //private List lines = new ArrayList<>(); - private Group lines = new Group(); + private Group lineGroup = new Group(); + private Group wakeGroup = new Group(); private Polygon boatPoly; -// private Polygon wakePoly; + private Polygon wakePoly; private Text teamNameObject; private Text velocityObject; - private Wake2 wake; + private Wake wake; public BoatGroup (Boat boat, Color color){ this.boat = boat; @@ -56,16 +52,6 @@ public class BoatGroup extends RaceObject{ private void initChildren (Color color, double... points) { boatPoly = new Polygon(points); boatPoly.setFill(color); -// boatPoly.setLayoutX(0); -// boatPoly.setLayoutY(0); -// boatPoly.relocate(boatPoly.getLayoutX(), boatPoly.getLayoutY()); -// -// wakePoly = new Polygon( -// 5.0,0.0, -// 10.0, boat.getVelocity() * VELOCITY_WAKE_RATIO, -// 0.0, boat.getVelocity() * VELOCITY_WAKE_RATIO -// ); -// wakePoly.setFill(Color.DARKBLUE); teamNameObject = new Text(boat.getShortName()); velocityObject = new Text(String.valueOf(boat.getVelocity())); @@ -77,10 +63,11 @@ public class BoatGroup extends RaceObject{ velocityObject.setX(VELOCITY_X_OFFSET); velocityObject.setY(VELOCITY_Y_OFFSET); velocityObject.relocate(velocityObject.getX(), velocityObject.getY()); + destinationSet = false; - wake = new Wake2(0, 0, 0); -// super.getChildren().addAll(wakePoly, boatPoly, teamNameObject, velocityObject); - super.getChildren().addAll(lines, wake, teamNameObject, velocityObject, boatPoly); + wake = new Wake(0, 0); + wakeGenerationDelay = wake.numWakes; + super.getChildren().addAll(teamNameObject, velocityObject, boatPoly); } private void initChildren (Color color) { @@ -102,9 +89,9 @@ public class BoatGroup extends RaceObject{ teamNameObject.setLayoutY(teamNameObject.getLayoutY() + dy); velocityObject.setLayoutX(velocityObject.getLayoutX() + dx); velocityObject.setLayoutY(velocityObject.getLayoutY() + dy); -// wakePoly.setLayoutX(wakePoly.getLayoutX() + dx); -// wakePoly.setLayoutY(wakePoly.getLayoutY() + dy); - rotateTo(currentRotation); + wake.setLayoutX(wake.getLayoutX() + dx); + wake.setLayoutY(wake.getLayoutY() + dy); + rotateTo(rotation + currentRotation); } /** @@ -124,55 +111,19 @@ public class BoatGroup extends RaceObject{ teamNameObject.setLayoutY(y); velocityObject.setLayoutX(x); velocityObject.setLayoutY(y); - wake.moveTo(x, y, currentRotation); -// wakePoly.setLayoutX(x); -// wakePoly.setLayoutY(y); + wake.setLayoutX(x + BOAT_WIDTH / 2); + wake.setLayoutY(y); + wake.rotate(currentRotation); } public void updatePosition (long timeInterval) { double dx = pixelVelocityX * timeInterval; double dy = pixelVelocityY * timeInterval; double rotation = 0d; - if (rotationalGoal > currentRotation && rotationalVelocity > 0) { - rotation = rotationalVelocity * timeInterval; - } else if (rotationalGoal < currentRotation && rotationalVelocity < 0) { - rotation = rotationalVelocity * timeInterval; - } + moveGroupBy(dx, dy, rotation); -// if (super.getChildren().size() > 3) { -// for (Node wake : super.getChildren().subList(4, super.getChildren().size())) { -// if (!((Wake) wake).updatePosition(timeInterval)) -// super.getChildren().remove(wake); -// } -// } -// for (Wake wake : wakes) { -// if (wake.updatePosition(timeInterval)) { -// super.getChildren().remove(wake); -// } -// } -// if (wakeCounter-- == 0) { -//// if (boat.getShortName().equals("BAR")) -//// System.out.println("thinking"); -// wakeCounter = WAKE_FRAME_INTERVAL; -// if (pixelVelocityX > 0 && pixelVelocityY > 0) { -//// super.getChildren().add( -//// new Wake( -//// super.getLayoutX() + BOAT_HEIGHT, super.getLayoutY() + BOAT_HEIGHT, pixelVelocityX, pixelVelocityY -//// ) -//// ); -// Wake wake = new Wake( -// boatPoly.getLayoutX(), -// boatPoly.getLayoutY(), -// pixelVelocityX, -// pixelVelocityY, rotation); -//// wake.getTransforms().clear(); -//// wake.getTransforms().add(new Rotate(rotation, 0, 0)); -// super.getChildren().add(wake); -// wakes.add(wake); -// } -// -// } - if (framesForNewLine == 0) { + + if (framesForNewLine-- == 0) { framesForNewLine = LINE_INTERVAL; if (lastPoint != null) { Line l = new Line( @@ -183,28 +134,38 @@ public class BoatGroup extends RaceObject{ ); l.getStrokeDashArray().setAll(4d, 6d); l.setStroke(boatPoly.getFill()); - //lines.add(l); - lines.getChildren().add(l); + lineGroup.getChildren().add(l); } - lastPoint = new Point2D(boatPoly.getLayoutX(), boatPoly.getLayoutY()); + if (destinationSet){ + lastPoint = new Point2D(boatPoly.getLayoutX(), boatPoly.getLayoutY()); + } + if (lineGroup.getChildren().size() > 100) + lineGroup.getChildren().remove(0); } - framesForNewLine -= 1; wake.updatePosition(timeInterval); } public void setDestination (double newXValue, double newYValue, double rotation, int... raceIds) { - //System.out.println("MADE IT"); + destinationSet = true; + boat.setVelocity(StreamParser.boatSpeeds.get((long)boat.getId())); if (hasRaceId(raceIds)) { this.pixelVelocityX = (newXValue - boatPoly.getLayoutX()) / expectedUpdateInterval; this.pixelVelocityY = (newYValue - boatPoly.getLayoutY()) / expectedUpdateInterval; this.rotationalGoal = rotation; calculateRotationalVelocity(); rotateTo(rotation); - wake.setRotationalVelocity(rotationalVelocity); + if (wakeGenerationDelay > 0) { + wake.rotate(rotationalGoal); + wakeGenerationDelay--; + } else { + wake.setRotationalVelocity(rotationalVelocity); + } } } public void setDestination (double newXValue, double newYValue, int... raceIDs) { + destinationSet = true; + if (hasRaceId(raceIDs)) { double rotation = Math.abs( Math.toDegrees( @@ -213,41 +174,43 @@ public class BoatGroup extends RaceObject{ ) ) ); - -// if (boatPoly.getLayoutY() >= newYValue && boatPoly.getLayoutX() <= newXValue) -// rotation = 90 - rotation; -// else if (boatPoly.getLayoutY() < newYValue && boatPoly.getLayoutX() <= newXValue) -// rotation = 90 + rotation; -// else if (boatPoly.getLayoutY() >= newYValue && boatPoly.getLayoutX() > newXValue) -// rotation = 270 + rotation; -// else -// rotation = 270 - rotation; setDestination(newXValue, newYValue, rotation, raceIDs); } } - public void rotateTo (double rotation) { - //if(rotation != 0) { - //rotationalGoal = rotation; - currentRotation = rotation; - boatPoly.getTransforms().clear(); - boatPoly.getTransforms().add(new Rotate(rotation, BOAT_WIDTH / 2, 0)); - //} -// wakePoly.getTransforms().clear(); -// wakePoly.getTransforms().add(new Rotate(rotation, 0, 0)); + void resizeWake(){ + velocityObject.setText(String.valueOf(boat.getVelocity())); + super.getChildren().remove(wakePoly); + wakePoly = new Polygon( + 5.0,0.0, + 10.0, boat.getVelocity() * VELOCITY_WAKE_RATIO, + 0.0, boat.getVelocity() * VELOCITY_WAKE_RATIO + ); + wakePoly.setLayoutX(boatPoly.getLayoutX()); + wakePoly.setLayoutY(boatPoly.getLayoutY()); + wakePoly.setFill(Color.DARKBLUE); + super.getChildren().add(wakePoly); + } + public void rotateTo (double rotation) { + currentRotation = rotation; + boatPoly.getTransforms().clear(); + boatPoly.getTransforms().add(new Rotate(rotation, BOAT_WIDTH / 2, 0)); + } + + + public void forceRotation () { rotateTo (rotationalGoal); + wake.rotate(rotationalGoal); } public void toggleAnnotations () { teamNameObject.setVisible(!teamNameObject.isVisible()); velocityObject.setVisible(!velocityObject.isVisible()); - for (Wake wake : wakes) { - wake.setVisible(!wake.isVisible()); - } - lines.setVisible(!lines.isVisible()); + lineGroup.setVisible(!lineGroup.isVisible()); + wake.setVisible(!wake.isVisible()); } public Boat getBoat() { @@ -265,4 +228,10 @@ public class BoatGroup extends RaceObject{ public int[] getRaceIds () { return new int[] {boat.getId()}; } + + public Group getLowPriorityAnnotations () { + Group group = new Group(); + group.getChildren().addAll(wake, lineGroup); + return group; + } } diff --git a/src/main/java/seng302/models/RaceObject.java b/src/main/java/seng302/models/RaceObject.java index 9ec22fd5..450b11b8 100644 --- a/src/main/java/seng302/models/RaceObject.java +++ b/src/main/java/seng302/models/RaceObject.java @@ -4,11 +4,12 @@ import javafx.geometry.Point2D; import javafx.scene.Group; /** - * Created by CJIRWIN on 26/04/2017. + * RaceObject defines the behaviour that animated objects whose position is updated from a yacht race data stream must + * adhere to. */ public abstract class RaceObject extends Group { - //Time between sections of race - Should be changed to 200 for actual program. + //Time between sections of race protected static double expectedUpdateInterval = 200; protected double rotationalGoal; @@ -17,10 +18,6 @@ public abstract class RaceObject extends Group { protected double pixelVelocityX; protected double pixelVelocityY; - public boolean isSamePos (Point2D point) { - return point.getX() == super.getLayoutX() && point.getY() == super.getLayoutY(); - } - public Point2D getPosition () { return new Point2D(super.getLayoutX(), getLayoutY()); } @@ -45,6 +42,14 @@ public abstract class RaceObject extends Group { } } + /** + * Sets the destination of everything within the RaceObject that has an ID in the array raceIds. The destination is + * set to the co-ordinates (x, y) with the given rotation. + * @param x + * @param y + * @param rotation + * @param raceIds + */ public abstract void setDestination (double x, double y, double rotation, int... raceIds); public abstract void setDestination (double x, double y, int... raceIds); diff --git a/src/main/java/seng302/models/Wake.java b/src/main/java/seng302/models/Wake.java index 92b2d30d..9a4e3d5d 100644 --- a/src/main/java/seng302/models/Wake.java +++ b/src/main/java/seng302/models/Wake.java @@ -1,51 +1,94 @@ package seng302.models; +import javafx.scene.Group; import javafx.scene.paint.Color; import javafx.scene.shape.Arc; import javafx.scene.shape.ArcType; import javafx.scene.transform.Rotate; -import javafx.scene.transform.Translate; /** - * Created by CJIRWIN on 27/04/2017. + * By default wake is a group containing 5 arcs. Each arc starts from the same point. Each arc is larger and more + * transparent than the last. On calling updatePositions() arcs rotate at velocities given by setRotationalVelocity(). + * The larger and more transparent an arc is the longer the delay before it rotates at the latest velocity. It is + * assumed that rotationalVelocities() are set regularly as wakes do not stop rotating and an array of velocities needs + * to be populated for the class to work as expected. */ -class Wake extends Arc { +class Wake extends Group { + private final double OPACITY_INCREASE = -0.10; + private final double RADIUS_INCREASE = 10; + final int numWakes = 5; + private double[] velocities = new double[numWakes * 3]; + private Arc[] arcs = new Arc[numWakes]; + private double[] rotations = new double[numWakes]; + private int velocitiesIndex = 0; - private static int VELOCITY_SCALE_FACTOR = 3; - private static int MAX_LIFESPAN = 210; - private static double LIFESPAN_PER_FRAME = 1.0 / MAX_LIFESPAN; - //private static double LENGTH_PER_FRAME = 120 / MAX_LIFESPAN; - private static double LENGTH_PER_FRAME = 0.25; - - private double velocityX; - private double velocityY; - private double opacity; - private int lifespan = MAX_LIFESPAN; - - Wake (double startingX, double startingY, double velocityX, double velocityY, double rotation) { - super(startingX, startingY, 20, 30, 180, 0); - //super.setFill(Color.BLUE); - super.setStroke(Color.DEEPSKYBLUE); - super.setType(ArcType.OPEN); - super.setFill(new Color(0, 0, 0 ,0)); - super.setStrokeWidth(2.0); - super.getTransforms().add(new Rotate(rotation, 5, -15)); -// this.velocityX = -velocityX; -// this.velocityY = -velocityY; - this.velocityX = 0; - this.velocityY = 0; + /** + * Create a wake at the given location. + * @param startingX x location where the tip of wake arcs will be. + * @param startingY y location where the tip of wake arcs will be. + */ + Wake(double startingX, double startingY) { + super.setLayoutX(startingX); + super.setLayoutY(startingY); + Arc arc; + for (int i = 0; i < numWakes; i++) { + arc = new Arc( + 0, + 0, + 30 + RADIUS_INCREASE * i, + 30 + RADIUS_INCREASE * i, + -110, + 40 + ); + arc.setFill(new Color(0.18, 0.7, 1.0, 0.50 + OPACITY_INCREASE * i)); + arc.setType(ArcType.ROUND); + arcs[i] = arc; + } + super.getChildren().addAll(arcs); } - boolean updatePosition (double timeInterval) { - lifespan--; - //super.setOpacity(LIFESPAN_PER_FRAME * lifespan * super.getOpacity()); - //opacity = LIFESPAN_PER_FRAME * lifespan * opacity; - //super.setFill(new Color(0.0f, 0.0f, 1.0f, opacity)); - super.setLayoutX(super.getLayoutX() + velocityX * timeInterval); - super.setLayoutY(super.getLayoutY() + velocityY * timeInterval); - super.setStartAngle(super.getStartAngle() - LENGTH_PER_FRAME); - super.setLength(super.getLength() + LENGTH_PER_FRAME * 2); - return lifespan < 0; + /** + * Sets the rotationalVelocity of each arc. Each arc is 3 velocities behind the next smallest arc. The smallest uses + * the latest given velocity. + * @param rotationalVelocity The rotationalVelocity the wake should move at. + */ + void setRotationalVelocity (double rotationalVelocity) { + velocitiesIndex = (velocitiesIndex + 1) % 14; + velocities[velocitiesIndex] = rotationalVelocity; + } + + /** + * Arcs rotate based on the distance they would have travelled over the supplied time interval. + * @param timeInterval the time interval, in microseconds, that the wake should move. + */ + void updatePosition (long timeInterval) { + int temp = velocitiesIndex; + for (int i = 0; i < arcs.length; i++) { + //temp = ((temp + 3) % 14); + rotations[i] = rotations[i] + velocities[temp] * timeInterval; + int j = 0; + //I have no idea why I have to do this to make it work. + //I will buy you a block of chocolate if you can tell me why. + switch (i) { + case 4: + j = 1; break; + case 3: + j = 2; break; + case 2: + j = 3; break; + case 1: + j = 4; break; + } + arcs[j].getTransforms().setAll(new Rotate(rotations[i])); + temp = ((temp + 3) % 14); + } + } + + void rotate (double rotation) { + for (int i = 0; i < arcs.length; i++) { + rotations[i] = rotation; + arcs[i].getTransforms().setAll(new Rotate(rotation)); + } } } diff --git a/src/main/java/seng302/models/Wake2.java b/src/main/java/seng302/models/Wake2.java deleted file mode 100644 index 98090a45..00000000 --- a/src/main/java/seng302/models/Wake2.java +++ /dev/null @@ -1,86 +0,0 @@ -package seng302.models; - -import javafx.scene.paint.Color; -import javafx.scene.shape.Arc; -import javafx.scene.transform.Rotate; - -/** - * Created by cir27 on 28/04/17. - */ -class Wake2 extends Arc { - private final double OPACITY_INCREASE = -0.2; - private final double RADIUS_INCREASE = 10; - private final int DELAY = 60; - private double opacity; - private double rotationalVelocity; - private Wake2 subWake; - private boolean hasSubWake = false; - private int delayCount = DELAY; - - Wake2 (double startingX, double startingY, double rotationalVelocity) { - super(startingX, startingY, 30, 30, 150, 60); - opacity = 1.0; - super.setFill(new Color(0.0, 0.0, 0.0, opacity)); - subWake = new Wake2( - startingX, - startingY, - super.getRadiusX() + RADIUS_INCREASE, - rotationalVelocity, - opacity + OPACITY_INCREASE - ); - hasSubWake = true; - this.rotationalVelocity = rotationalVelocity; - - } - Wake2 (double startingX, double startingY, double radius, double rotationalVelocity, double opacity) { - super(startingX, startingY, radius, radius, 150, 60); - super.setFill(new Color(0.0, 0.0, 0.0, opacity)); - this.opacity = opacity; - if (!(opacity < 0)) { - subWake = new Wake2( - startingX, - startingY, - super.getRadiusX() + RADIUS_INCREASE, - rotationalVelocity, - opacity + OPACITY_INCREASE - ); - hasSubWake = true; - } - this.rotationalVelocity = rotationalVelocity; - } - - void setRotationalVelocity (double rotationalVelocity) { - this.rotationalVelocity = rotationalVelocity; - delayCount = DELAY; - } - - void updatePosition (long timeInterval) { - if (delayCount-- == 0) - subWake.setRotationalVelocity(rotationalVelocity); - super.getTransforms().clear(); - super.getTransforms().add( - new Rotate( - rotationalVelocity * timeInterval, - super.getCenterX(), - super.getCenterY() - ) - ); - if(hasSubWake) - subWake.updatePosition(timeInterval); - } - - void moveTo (double x, double y, double rotation) { - super.setLayoutX(x); - super.setLayoutY(y); - super.getTransforms().clear(); - super.getTransforms().add( - new Rotate( - rotation, - super.getCenterX(), - super.getCenterY() - ) - ); - if(hasSubWake) - subWake.moveTo(x, y, rotation); - } -} diff --git a/src/main/java/seng302/models/parsers/StreamParser.java b/src/main/java/seng302/models/parsers/StreamParser.java index f49e4d31..766296a7 100644 --- a/src/main/java/seng302/models/parsers/StreamParser.java +++ b/src/main/java/seng302/models/parsers/StreamParser.java @@ -1,12 +1,10 @@ package seng302.models.parsers; -import javafx.geometry.Point2D; import javafx.geometry.Point3D; import org.w3c.dom.Document; import org.xml.sax.InputSource; import org.xml.sax.SAXException; -import sun.awt.UNIXToolkit; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; @@ -14,19 +12,24 @@ import javax.xml.parsers.ParserConfigurationException; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.StringReader; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.*; import java.util.concurrent.ConcurrentHashMap; /** + * The purpose of this class is to take in the stream of divided packets so they can be read + * and parsed in by turning the byte arrays into useful data. There are two public static hashmaps + * that are threadsafe so the visualiser can always access the latest speed and position avaible * Created by kre39 on 23/04/17. */ public class StreamParser extends Thread{ public static ConcurrentHashMap boatPositions = new ConcurrentHashMap<>(); - private static ArrayList boat_IDS = new ArrayList<>(); - private String threadName; + public static ConcurrentHashMap boatSpeeds = new ConcurrentHashMap<>(); + private String threadName; private Thread t; private static boolean raceStarted = false; @@ -34,7 +37,11 @@ public class StreamParser extends Thread{ this.threadName = threadName; } - public void run(){ + /** + * Used to within threading so when the stream parser thread runs, it will keep looking for a packet to + * process until it is unable to find anymore packets + */ + public void run(){ try { System.out.println("START OF STREAM"); while (StreamReceiver.packetBuffer == null || StreamReceiver.packetBuffer.size() < 1) { @@ -54,6 +61,9 @@ public class StreamParser extends Thread{ } } + /** + * Used to start the stream parser thread when multithreading + */ public void start () { System.out.println("Starting " + threadName ); if (t == null) { @@ -62,7 +72,7 @@ public class StreamParser extends Thread{ } } - static void parsePacket(StreamPacket packet) { + private static void parsePacket(StreamPacket packet) { switch (packet.getType()){ case HEARTBEAT: extractHeartBeat(packet); @@ -106,12 +116,20 @@ public class StreamParser extends Thread{ } } + /** + * Extracts the seq num used in the heartbeat packet + * @param packet Packet parsed in to use the payload + */ private static void extractHeartBeat(StreamPacket packet) { long heartbeat = bytesToLong(packet.getPayload()); -// System.out.println("Heartbeat: " + heartbeat); - } + /** + * Extracts the useful race status data from race status type packets. This method will also print to the + * console the current state of the race (if it has started/finished or is about to start), along side + * this it'll also display the amount of time since the race has started or time till it starts + * @param packet Packet parsed in to use the payload + */ private static void extractRaceStatus(StreamPacket packet){ byte[] payload = packet.getPayload(); int messageVersionNo = payload[0]; @@ -155,6 +173,10 @@ public class StreamParser extends Thread{ } } + /** + * Used to extract the messages passed through with the display message packet + * @param packet Packet parsed in to use the payload + */ private static void extractDisplayMessage(StreamPacket packet){ byte[] payload = packet.getPayload(); int messageVersionNo = payload[0]; @@ -169,7 +191,11 @@ public class StreamParser extends Thread{ } } - static void extractXmlMessage(StreamPacket packet){ + /** + * Used to read in the xml data. Will call the specific methods to create the course and boats + * @param packet Packet parsed in to use the payload + */ + private static void extractXmlMessage(StreamPacket packet){ byte[] payload = packet.getPayload(); String xmlMessage = ""; @@ -197,16 +223,17 @@ public class StreamParser extends Thread{ db = dbf.newDocumentBuilder(); Document doc = db.parse(new InputSource(new StringReader(xmlMessage))); // TODO: 25/04/17 ajm412: Check that the object matches expected structure and return Document object. - } catch (ParserConfigurationException e) { - e.printStackTrace(); - } catch (SAXException e) { - e.printStackTrace(); - } catch (IOException e) { + } catch (ParserConfigurationException | IOException | SAXException e) { e.printStackTrace(); } } + /** + * Extracts the race start status from the packet, currently is unused within the app but + * is here for potential future use + * @param packet Packet parsed in to use the payload + */ private static void extractRaceStartStatus(StreamPacket packet){ byte[] payload = packet.getPayload(); int messageVersionNo = payload[0]; @@ -216,6 +243,11 @@ public class StreamParser extends Thread{ int notificationType = payload[19]; } + /** + * When a yacht event occurs this will parse the byte array to retrieve the necessary info, + * currently unused + * @param packet Packet parsed in to use the payload + */ private static void extractYachtEventCode(StreamPacket packet){ byte[] payload = packet.getPayload(); int messageVersionNo = payload[0]; @@ -226,6 +258,11 @@ public class StreamParser extends Thread{ int eventId = payload[21]; } + /** + * When a yacht action occurs this will parse the parse the byte array to retrieve the necessary info, + * currently unused + * @param packet Packet parsed in to use the payload + */ private static void extractYachtActionCode(StreamPacket packet){ byte[] payload = packet.getPayload(); int messageVersionNo = payload[0]; @@ -236,6 +273,10 @@ public class StreamParser extends Thread{ // System.out.println("eventId = " + eventId); } + /** + * Strips the message from the chatter text type packets, currently the message is unused + * @param packet Packet parsed in to use the payload + */ private static void extractChatterText(StreamPacket packet){ byte[] payload = packet.getPayload(); int messageVersionNo = payload[0]; @@ -244,8 +285,12 @@ public class StreamParser extends Thread{ String message = new String(Arrays.copyOfRange(payload,3,3 + length)); } - - static void extractBoatLocation(StreamPacket packet){ + /** + * Used to breakdown the boatlocation packets so the boat coordinates, id and groundspeed are all used + * All the other extra data is still being read and translated however is unused. + * @param packet Packet parsed in to use the payload + */ + private static void extractBoatLocation(StreamPacket packet){ byte[] payload = packet.getPayload(); byte deviceType = payload[15]; byte[] seqBytes = Arrays.copyOfRange(payload,11,15); @@ -253,6 +298,8 @@ public class StreamParser extends Thread{ byte[] lonBytes = Arrays.copyOfRange(payload,20,24); byte[] boatIdBytes = Arrays.copyOfRange(payload,7,11); byte[] headingBytes = Arrays.copyOfRange(payload,28,30); + byte[] groundSpeedBytes = Arrays.copyOfRange(payload,38,40); + long timeStamp = extractTimeStamp(Arrays.copyOfRange(payload,1,7), 6); // int boatSeq = ByteBuffer.wrap(seqBytes).getInt(); long seq = bytesToLong(seqBytes); @@ -260,7 +307,9 @@ public class StreamParser extends Thread{ long lat = bytesToLong(latBytes); long lon = bytesToLong(lonBytes); long heading = bytesToLong(headingBytes); - +// long speed = extractTimeStamp(speedBytes, 2); + double groundSpeed = bytesToLong(groundSpeedBytes)/1000.0; + short s = (short) ((groundSpeedBytes[1] & 0xFF) << 8 | (groundSpeedBytes[0] & 0xFF)); if ((int)deviceType == 1 || (int)deviceType == 4){ // System.out.println("boatId = " + boatId); // System.out.println("deviceType = " + (long)deviceType); @@ -268,13 +317,16 @@ public class StreamParser extends Thread{ //needs to be validated Point3D point = new Point3D(((180d * (double)lat)/Math.pow(2,31)),((180d *(double)lon)/Math.pow(2,31)),(double)heading); boatPositions.putIfAbsent(boatId, point); + boatSpeeds.putIfAbsent(boatId, groundSpeed); boatPositions.replace(boatId, point); + boatSpeeds.replace(boatId, groundSpeed); // System.out.println("lon = " + ((180d * (double)lon)/Math.pow(2,31))); // System.out.println("lat = " + ((180d *(double)lat)/Math.pow(2,31))); } } + private static void extractMarkRounding(StreamPacket packet){ byte[] payload = packet.getPayload(); int messageVersionNo = payload[0]; diff --git a/src/main/resources/config/teams.xml b/src/main/resources/config/teams.xml index f8d1e58d..0ac01cac 100644 --- a/src/main/resources/config/teams.xml +++ b/src/main/resources/config/teams.xml @@ -4,37 +4,37 @@ Oracle Team USA USA - 12.9 + 0.0 102 Artemis Racing ART - 13.1 + 0.0 101 Emirates Team New Zealand NZL - 15.6 + 0.0 103 Land Rover BAR BAR - 13.3 + 0.0 104 SoftBank Team Japan JAP - 14.7 + 0.0 105 Groupama Team France FRC - 11.4 + 0.0 106 \ No newline at end of file diff --git a/src/main/resources/views/RaceView.fxml b/src/main/resources/views/RaceView.fxml index f7fcbfeb..2cae1326 100644 --- a/src/main/resources/views/RaceView.fxml +++ b/src/main/resources/views/RaceView.fxml @@ -1,15 +1,18 @@ - - - - - - - + + + + + + + + + + - +