Further work on new wake system. Wakes turn correctly but need to scale with velocity and

eventually desync with the boats. Needs to reset to the boats position on straights.
This commit is contained in:
Calum
2017-04-28 23:25:49 +12:00
parent 765f27f987
commit 474f0ee427
10 changed files with 296 additions and 267 deletions
@@ -11,6 +11,8 @@ import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext; import javafx.scene.canvas.GraphicsContext;
import javafx.scene.layout.AnchorPane; import javafx.scene.layout.AnchorPane;
import javafx.scene.paint.Color; import javafx.scene.paint.Color;
import javafx.scene.shape.Arc;
import javafx.scene.shape.ArcType;
import javafx.scene.text.Font; import javafx.scene.text.Font;
import javafx.util.Pair; import javafx.util.Pair;
import seng302.models.Boat; import seng302.models.Boat;
@@ -22,6 +24,7 @@ import seng302.models.parsers.StreamParser;
import seng302.models.parsers.StreamReceiver; import seng302.models.parsers.StreamReceiver;
import java.sql.Time; import java.sql.Time;
import java.text.DecimalFormat;
import java.util.*; import java.util.*;
/** /**
@@ -59,6 +62,13 @@ public class CanvasController {
private double metersToPixels; private double metersToPixels;
private List<RaceObject> raceObjects = new ArrayList<>(); private List<RaceObject> 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; public AnimationTimer timer;
private enum ScaleDirection { private enum ScaleDirection {
@@ -80,8 +90,8 @@ public class CanvasController {
// Bind canvas size to stack pane size. // Bind canvas size to stack pane size.
canvas.widthProperty().bind(new SimpleDoubleProperty(CANVAS_WIDTH)); canvas.widthProperty().bind(new SimpleDoubleProperty(CANVAS_WIDTH));
canvas.heightProperty().bind(new SimpleDoubleProperty(CANVAS_HEIGHT)); canvas.heightProperty().bind(new SimpleDoubleProperty(CANVAS_HEIGHT));
group.minWidth(CANVAS_WIDTH); //group.minWidth(CANVAS_WIDTH);
group.minHeight(CANVAS_HEIGHT); //group.minHeight(CANVAS_HEIGHT);
} }
public void initializeCanvas (){ public void initializeCanvas (){
@@ -138,6 +148,8 @@ public class CanvasController {
timer = new AnimationTimer() { timer = new AnimationTimer() {
private int countdown = 60; private int countdown = 60;
private int[] currentRaceMarker = {1, 1, 1, 1, 1, 1}; private int[] currentRaceMarker = {1, 1, 1, 1, 1, 1};
List<Mark> marks = raceViewController.getRace().getCourse(); List<Mark> marks = raceViewController.getRace().getCourse();
@@ -150,6 +162,20 @@ public class CanvasController {
int boatIndex = 0; int boatIndex = 0;
Mark nextMark; 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) { //if (countdown == 0) {
//System.out.println("called the at"); //System.out.println("called the at");
for (RaceObject raceObject : raceObjects) { for (RaceObject raceObject : raceObjects) {
@@ -163,13 +189,10 @@ public class CanvasController {
//descending = nextMark.getY() > boatGroup.getLayoutY(); //descending = nextMark.getY() > boatGroup.getLayoutY();
//leftToRight = nextMark.getX() < boatGroup.getLayoutX(); //leftToRight = nextMark.getX() < boatGroup.getLayoutX();
raceObject.updatePosition(1000 / 60); raceObject.updatePosition(1000 / 60);
for (int id : raceObject.getRaceIds()) { for (int id : raceObject.getRaceIds()) {
//System.out.println("id = " + id); //System.out.println("id = " + id);
if (id != 0 && StreamParser.boatPositions.size() > 0) { if (id != 0 && StreamParser.boatPositions.size() > 0) {
boolean test = StreamParser.boatPositions.containsKey(id);
if (StreamParser.boatPositions.containsKey((long) id)) { if (StreamParser.boatPositions.containsKey((long) id)) {
Point3D p = StreamParser.boatPositions.get((long) id); Point3D p = StreamParser.boatPositions.get((long) id);
Point2D p2d = latLonToXY(p.getX(), p.getY()); Point2D p2d = latLonToXY(p.getX(), p.getY());
@@ -285,10 +308,15 @@ public class CanvasController {
private void drawFps(int fps){ private void drawFps(int fps){
if (raceViewController.isDisplayFps()){ if (raceViewController.isDisplayFps()){
gc.clearRect(5,5,50,20);
gc.setFill(Color.BLACK); gc.setFill(Color.BLACK);
gc.setFont(new Font(14)); gc.setFont(new Font(14));
gc.setLineWidth(3); gc.setLineWidth(3);
gc.fillText(fps + " FPS", 5, 20); 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 startingY = raceObjects.get(0).getLayoutY();
Double firstMarkX = raceObjects.get(1).getLayoutX(); Double firstMarkX = raceObjects.get(1).getLayoutX();
Double firstMarkY = raceObjects.get(1).getLayoutY(); 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) { for (Boat boat : boats) {
BoatGroup boatGroup = new BoatGroup(boat, Colors.getColor()); BoatGroup boatGroup = new BoatGroup(boat, Colors.getColor());
System.out.println("MADE A BOAT GROUP FOR " + boatGroup.getBoat().getShortName());
boatGroup.moveTo(startingX, startingY, 0d); boatGroup.moveTo(startingX, startingY, 0d);
boatGroup.setDestination(firstMarkX, firstMarkY); // boatGroup.setDestination(firstMarkX, firstMarkY);
boatGroup.forceRotation(); boatGroup.forceRotation();
group.getChildren().add(boatGroup); //group.getChildren().add(boatGroup);
raceObjects.add(boatGroup); raceObjects.add(boatGroup);
boatAnnotations.getChildren().add(boatGroup.getLowPriorityAnnotations());
// drawBoat(boat.getLongitude(), boat.getLatitude(), boat.getColor(), boat.getShortName(), boat.getSpeedInKnots(), boat.getHeading()); // 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()); // gateMark.getSingleMark2().setY((int) canvasLocation.getY());
markGroup = new MarkGroup(mark, findScaledXY(gateMark.getSingleMark1()), findScaledXY(gateMark.getSingleMark2())); markGroup = new MarkGroup(mark, findScaledXY(gateMark.getSingleMark1()), findScaledXY(gateMark.getSingleMark2()));
group.getChildren().add(markGroup); //group.getChildren().add(markGroup);
raceObjects.add(markGroup); raceObjects.add(markGroup);
} else { } else {
// canvasLocation = findScaledXY(mark); // canvasLocation = findScaledXY(mark);
// mark.setX((int) canvasLocation.getX()); // mark.setX((int) canvasLocation.getX());
// mark.setY((int) canvasLocation.getY()); // mark.setY((int) canvasLocation.getY());
markGroup = new MarkGroup(mark, findScaledXY(mark)); markGroup = new MarkGroup(mark, findScaledXY(mark));
group.getChildren().add(markGroup); raceObjects.add(markGroup);
//group.getChildren().add(markGroup);
} }
processed.add(mark); processed.add(mark);
} }
@@ -5,6 +5,7 @@ import javafx.animation.KeyFrame;
import javafx.animation.Timeline; import javafx.animation.Timeline;
import javafx.beans.value.ChangeListener; import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue; import javafx.beans.value.ObservableValue;
import javafx.event.ActionEvent;
import javafx.fxml.FXML; import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader; import javafx.fxml.FXMLLoader;
import javafx.scene.control.CheckBox; import javafx.scene.control.CheckBox;
+1
View File
@@ -140,4 +140,5 @@ public class Boat {
public int getId() { public int getId() {
return id; return id;
} }
} }
+68 -99
View File
@@ -2,16 +2,12 @@ package seng302.models;
import javafx.geometry.Point2D; import javafx.geometry.Point2D;
import javafx.scene.Group; import javafx.scene.Group;
import javafx.scene.Node;
import javafx.scene.paint.Color; import javafx.scene.paint.Color;
import javafx.scene.shape.Line; import javafx.scene.shape.Line;
import javafx.scene.shape.Polygon; import javafx.scene.shape.Polygon;
import javafx.scene.text.Text; import javafx.scene.text.Text;
import javafx.scene.transform.Rotate; import javafx.scene.transform.Rotate;
import javafx.scene.transform.Translate; import seng302.models.parsers.StreamParser;
import java.util.ArrayList;
import java.util.List;
/** /**
* Created by CJIRWIN on 25/04/2017. * 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 VELOCITY_WAKE_RATIO = 2d;
private static final double BOAT_HEIGHT = 15d; private static final double BOAT_HEIGHT = 15d;
private static final double BOAT_WIDTH = 10d; private static final double BOAT_WIDTH = 10d;
private static final int LINE_INTERVAL = 120; private static final int LINE_INTERVAL = 180;
//Time between sections of race - Should be changed to 200 for actual program.
private static double expectedUpdateInterval = 200; private static double expectedUpdateInterval = 200;
private static int WAKE_FRAME_INTERVAL = 30; private static int WAKE_FRAME_INTERVAL = 30;
private double framesForNewLine = LINE_INTERVAL; private double framesForNewLine = 0;
private boolean destinationSet;
private Point2D lastPoint; private Point2D lastPoint;
private int wakeGenerationDelay;
private Boat boat; private Boat boat;
private int wakeCounter = WAKE_FRAME_INTERVAL; private int wakeCounter = WAKE_FRAME_INTERVAL;
private List<Wake> wakes = new ArrayList<>(); private Group lineGroup = new Group();
//private List<Line> lines = new ArrayList<>(); private Group wakeGroup = new Group();
private Group lines = new Group();
private Polygon boatPoly; private Polygon boatPoly;
// private Polygon wakePoly; private Polygon wakePoly;
private Text teamNameObject; private Text teamNameObject;
private Text velocityObject; private Text velocityObject;
private Wake2 wake; private Wake wake;
public BoatGroup (Boat boat, Color color){ public BoatGroup (Boat boat, Color color){
this.boat = boat; this.boat = boat;
@@ -56,16 +52,6 @@ public class BoatGroup extends RaceObject{
private void initChildren (Color color, double... points) { private void initChildren (Color color, double... points) {
boatPoly = new Polygon(points); boatPoly = new Polygon(points);
boatPoly.setFill(color); 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()); teamNameObject = new Text(boat.getShortName());
velocityObject = new Text(String.valueOf(boat.getVelocity())); velocityObject = new Text(String.valueOf(boat.getVelocity()));
@@ -77,10 +63,11 @@ public class BoatGroup extends RaceObject{
velocityObject.setX(VELOCITY_X_OFFSET); velocityObject.setX(VELOCITY_X_OFFSET);
velocityObject.setY(VELOCITY_Y_OFFSET); velocityObject.setY(VELOCITY_Y_OFFSET);
velocityObject.relocate(velocityObject.getX(), velocityObject.getY()); velocityObject.relocate(velocityObject.getX(), velocityObject.getY());
destinationSet = false;
wake = new Wake2(0, 0, 0); wake = new Wake(0, 0);
// super.getChildren().addAll(wakePoly, boatPoly, teamNameObject, velocityObject); wakeGenerationDelay = wake.numWakes;
super.getChildren().addAll(lines, wake, teamNameObject, velocityObject, boatPoly); super.getChildren().addAll(teamNameObject, velocityObject, boatPoly);
} }
private void initChildren (Color color) { private void initChildren (Color color) {
@@ -102,9 +89,9 @@ public class BoatGroup extends RaceObject{
teamNameObject.setLayoutY(teamNameObject.getLayoutY() + dy); teamNameObject.setLayoutY(teamNameObject.getLayoutY() + dy);
velocityObject.setLayoutX(velocityObject.getLayoutX() + dx); velocityObject.setLayoutX(velocityObject.getLayoutX() + dx);
velocityObject.setLayoutY(velocityObject.getLayoutY() + dy); velocityObject.setLayoutY(velocityObject.getLayoutY() + dy);
// wakePoly.setLayoutX(wakePoly.getLayoutX() + dx); wake.setLayoutX(wake.getLayoutX() + dx);
// wakePoly.setLayoutY(wakePoly.getLayoutY() + dy); wake.setLayoutY(wake.getLayoutY() + dy);
rotateTo(currentRotation); rotateTo(rotation + currentRotation);
} }
/** /**
@@ -124,55 +111,19 @@ public class BoatGroup extends RaceObject{
teamNameObject.setLayoutY(y); teamNameObject.setLayoutY(y);
velocityObject.setLayoutX(x); velocityObject.setLayoutX(x);
velocityObject.setLayoutY(y); velocityObject.setLayoutY(y);
wake.moveTo(x, y, currentRotation); wake.setLayoutX(x + BOAT_WIDTH / 2);
// wakePoly.setLayoutX(x); wake.setLayoutY(y);
// wakePoly.setLayoutY(y); wake.rotate(currentRotation);
} }
public void updatePosition (long timeInterval) { public void updatePosition (long timeInterval) {
double dx = pixelVelocityX * timeInterval; double dx = pixelVelocityX * timeInterval;
double dy = pixelVelocityY * timeInterval; double dy = pixelVelocityY * timeInterval;
double rotation = 0d; double rotation = 0d;
if (rotationalGoal > currentRotation && rotationalVelocity > 0) {
rotation = rotationalVelocity * timeInterval;
} else if (rotationalGoal < currentRotation && rotationalVelocity < 0) {
rotation = rotationalVelocity * timeInterval;
}
moveGroupBy(dx, dy, rotation); moveGroupBy(dx, dy, rotation);
// if (super.getChildren().size() > 3) {
// for (Node wake : super.getChildren().subList(4, super.getChildren().size())) { if (framesForNewLine-- == 0) {
// 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) {
framesForNewLine = LINE_INTERVAL; framesForNewLine = LINE_INTERVAL;
if (lastPoint != null) { if (lastPoint != null) {
Line l = new Line( Line l = new Line(
@@ -183,28 +134,38 @@ public class BoatGroup extends RaceObject{
); );
l.getStrokeDashArray().setAll(4d, 6d); l.getStrokeDashArray().setAll(4d, 6d);
l.setStroke(boatPoly.getFill()); l.setStroke(boatPoly.getFill());
//lines.add(l); lineGroup.getChildren().add(l);
lines.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); wake.updatePosition(timeInterval);
} }
public void setDestination (double newXValue, double newYValue, double rotation, int... raceIds) { 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)) { if (hasRaceId(raceIds)) {
this.pixelVelocityX = (newXValue - boatPoly.getLayoutX()) / expectedUpdateInterval; this.pixelVelocityX = (newXValue - boatPoly.getLayoutX()) / expectedUpdateInterval;
this.pixelVelocityY = (newYValue - boatPoly.getLayoutY()) / expectedUpdateInterval; this.pixelVelocityY = (newYValue - boatPoly.getLayoutY()) / expectedUpdateInterval;
this.rotationalGoal = rotation; this.rotationalGoal = rotation;
calculateRotationalVelocity(); calculateRotationalVelocity();
rotateTo(rotation); 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) { public void setDestination (double newXValue, double newYValue, int... raceIDs) {
destinationSet = true;
if (hasRaceId(raceIDs)) { if (hasRaceId(raceIDs)) {
double rotation = Math.abs( double rotation = Math.abs(
Math.toDegrees( 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); setDestination(newXValue, newYValue, rotation, raceIDs);
} }
} }
public void rotateTo (double rotation) { void resizeWake(){
//if(rotation != 0) { velocityObject.setText(String.valueOf(boat.getVelocity()));
//rotationalGoal = rotation; super.getChildren().remove(wakePoly);
currentRotation = rotation; wakePoly = new Polygon(
boatPoly.getTransforms().clear(); 5.0,0.0,
boatPoly.getTransforms().add(new Rotate(rotation, BOAT_WIDTH / 2, 0)); 10.0, boat.getVelocity() * VELOCITY_WAKE_RATIO,
//} 0.0, boat.getVelocity() * VELOCITY_WAKE_RATIO
// wakePoly.getTransforms().clear(); );
// wakePoly.getTransforms().add(new Rotate(rotation, 0, 0)); 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 () { public void forceRotation () {
rotateTo (rotationalGoal); rotateTo (rotationalGoal);
wake.rotate(rotationalGoal);
} }
public void toggleAnnotations () { public void toggleAnnotations () {
teamNameObject.setVisible(!teamNameObject.isVisible()); teamNameObject.setVisible(!teamNameObject.isVisible());
velocityObject.setVisible(!velocityObject.isVisible()); velocityObject.setVisible(!velocityObject.isVisible());
for (Wake wake : wakes) { lineGroup.setVisible(!lineGroup.isVisible());
wake.setVisible(!wake.isVisible()); wake.setVisible(!wake.isVisible());
}
lines.setVisible(!lines.isVisible());
} }
public Boat getBoat() { public Boat getBoat() {
@@ -265,4 +228,10 @@ public class BoatGroup extends RaceObject{
public int[] getRaceIds () { public int[] getRaceIds () {
return new int[] {boat.getId()}; return new int[] {boat.getId()};
} }
public Group getLowPriorityAnnotations () {
Group group = new Group();
group.getChildren().addAll(wake, lineGroup);
return group;
}
} }
+11 -6
View File
@@ -4,11 +4,12 @@ import javafx.geometry.Point2D;
import javafx.scene.Group; 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 { 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 static double expectedUpdateInterval = 200;
protected double rotationalGoal; protected double rotationalGoal;
@@ -17,10 +18,6 @@ public abstract class RaceObject extends Group {
protected double pixelVelocityX; protected double pixelVelocityX;
protected double pixelVelocityY; protected double pixelVelocityY;
public boolean isSamePos (Point2D point) {
return point.getX() == super.getLayoutX() && point.getY() == super.getLayoutY();
}
public Point2D getPosition () { public Point2D getPosition () {
return new Point2D(super.getLayoutX(), getLayoutY()); 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, double rotation, int... raceIds);
public abstract void setDestination (double x, double y, int... raceIds); public abstract void setDestination (double x, double y, int... raceIds);
+79 -36
View File
@@ -1,51 +1,94 @@
package seng302.models; package seng302.models;
import javafx.scene.Group;
import javafx.scene.paint.Color; import javafx.scene.paint.Color;
import javafx.scene.shape.Arc; import javafx.scene.shape.Arc;
import javafx.scene.shape.ArcType; import javafx.scene.shape.ArcType;
import javafx.scene.transform.Rotate; 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; * Create a wake at the given location.
private static double LIFESPAN_PER_FRAME = 1.0 / MAX_LIFESPAN; * @param startingX x location where the tip of wake arcs will be.
//private static double LENGTH_PER_FRAME = 120 / MAX_LIFESPAN; * @param startingY y location where the tip of wake arcs will be.
private static double LENGTH_PER_FRAME = 0.25; */
Wake(double startingX, double startingY) {
private double velocityX; super.setLayoutX(startingX);
private double velocityY; super.setLayoutY(startingY);
private double opacity; Arc arc;
private int lifespan = MAX_LIFESPAN; for (int i = 0; i < numWakes; i++) {
arc = new Arc(
Wake (double startingX, double startingY, double velocityX, double velocityY, double rotation) { 0,
super(startingX, startingY, 20, 30, 180, 0); 0,
//super.setFill(Color.BLUE); 30 + RADIUS_INCREASE * i,
super.setStroke(Color.DEEPSKYBLUE); 30 + RADIUS_INCREASE * i,
super.setType(ArcType.OPEN); -110,
super.setFill(new Color(0, 0, 0 ,0)); 40
super.setStrokeWidth(2.0); );
super.getTransforms().add(new Rotate(rotation, 5, -15)); arc.setFill(new Color(0.18, 0.7, 1.0, 0.50 + OPACITY_INCREASE * i));
// this.velocityX = -velocityX; arc.setType(ArcType.ROUND);
// this.velocityY = -velocityY; arcs[i] = arc;
this.velocityX = 0; }
this.velocityY = 0; super.getChildren().addAll(arcs);
} }
boolean updatePosition (double timeInterval) { /**
lifespan--; * Sets the rotationalVelocity of each arc. Each arc is 3 velocities behind the next smallest arc. The smallest uses
//super.setOpacity(LIFESPAN_PER_FRAME * lifespan * super.getOpacity()); * the latest given velocity.
//opacity = LIFESPAN_PER_FRAME * lifespan * opacity; * @param rotationalVelocity The rotationalVelocity the wake should move at.
//super.setFill(new Color(0.0f, 0.0f, 1.0f, opacity)); */
super.setLayoutX(super.getLayoutX() + velocityX * timeInterval); void setRotationalVelocity (double rotationalVelocity) {
super.setLayoutY(super.getLayoutY() + velocityY * timeInterval); velocitiesIndex = (velocitiesIndex + 1) % 14;
super.setStartAngle(super.getStartAngle() - LENGTH_PER_FRAME); velocities[velocitiesIndex] = rotationalVelocity;
super.setLength(super.getLength() + LENGTH_PER_FRAME * 2); }
return lifespan < 0;
/**
* 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));
}
} }
} }
-86
View File
@@ -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);
}
}
@@ -1,12 +1,10 @@
package seng302.models.parsers; package seng302.models.parsers;
import javafx.geometry.Point2D;
import javafx.geometry.Point3D; import javafx.geometry.Point3D;
import org.w3c.dom.Document; import org.w3c.dom.Document;
import org.xml.sax.InputSource; import org.xml.sax.InputSource;
import org.xml.sax.SAXException; import org.xml.sax.SAXException;
import sun.awt.UNIXToolkit;
import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.DocumentBuilderFactory;
@@ -14,19 +12,24 @@ import javax.xml.parsers.ParserConfigurationException;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
import java.io.IOException; import java.io.IOException;
import java.io.StringReader; import java.io.StringReader;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.text.DateFormat; import java.text.DateFormat;
import java.text.SimpleDateFormat; import java.text.SimpleDateFormat;
import java.util.*; import java.util.*;
import java.util.concurrent.ConcurrentHashMap; 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. * Created by kre39 on 23/04/17.
*/ */
public class StreamParser extends Thread{ public class StreamParser extends Thread{
public static ConcurrentHashMap<Long,Point3D> boatPositions = new ConcurrentHashMap<>(); public static ConcurrentHashMap<Long,Point3D> boatPositions = new ConcurrentHashMap<>();
private static ArrayList<Long> boat_IDS = new ArrayList<>(); public static ConcurrentHashMap<Long,Double> boatSpeeds = new ConcurrentHashMap<>();
private String threadName; private String threadName;
private Thread t; private Thread t;
private static boolean raceStarted = false; private static boolean raceStarted = false;
@@ -34,7 +37,11 @@ public class StreamParser extends Thread{
this.threadName = threadName; 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 { try {
System.out.println("START OF STREAM"); System.out.println("START OF STREAM");
while (StreamReceiver.packetBuffer == null || StreamReceiver.packetBuffer.size() < 1) { 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 () { public void start () {
System.out.println("Starting " + threadName ); System.out.println("Starting " + threadName );
if (t == null) { 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()){ switch (packet.getType()){
case HEARTBEAT: case HEARTBEAT:
extractHeartBeat(packet); 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) { private static void extractHeartBeat(StreamPacket packet) {
long heartbeat = bytesToLong(packet.getPayload()); 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){ private static void extractRaceStatus(StreamPacket packet){
byte[] payload = packet.getPayload(); byte[] payload = packet.getPayload();
int messageVersionNo = payload[0]; 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){ private static void extractDisplayMessage(StreamPacket packet){
byte[] payload = packet.getPayload(); byte[] payload = packet.getPayload();
int messageVersionNo = payload[0]; 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(); byte[] payload = packet.getPayload();
String xmlMessage = ""; String xmlMessage = "";
@@ -197,16 +223,17 @@ public class StreamParser extends Thread{
db = dbf.newDocumentBuilder(); db = dbf.newDocumentBuilder();
Document doc = db.parse(new InputSource(new StringReader(xmlMessage))); 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. // TODO: 25/04/17 ajm412: Check that the object matches expected structure and return Document object.
} catch (ParserConfigurationException e) { } catch (ParserConfigurationException | IOException | SAXException e) {
e.printStackTrace();
} catch (SAXException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace(); 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){ private static void extractRaceStartStatus(StreamPacket packet){
byte[] payload = packet.getPayload(); byte[] payload = packet.getPayload();
int messageVersionNo = payload[0]; int messageVersionNo = payload[0];
@@ -216,6 +243,11 @@ public class StreamParser extends Thread{
int notificationType = payload[19]; 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){ private static void extractYachtEventCode(StreamPacket packet){
byte[] payload = packet.getPayload(); byte[] payload = packet.getPayload();
int messageVersionNo = payload[0]; int messageVersionNo = payload[0];
@@ -226,6 +258,11 @@ public class StreamParser extends Thread{
int eventId = payload[21]; 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){ private static void extractYachtActionCode(StreamPacket packet){
byte[] payload = packet.getPayload(); byte[] payload = packet.getPayload();
int messageVersionNo = payload[0]; int messageVersionNo = payload[0];
@@ -236,6 +273,10 @@ public class StreamParser extends Thread{
// System.out.println("eventId = " + eventId); // 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){ private static void extractChatterText(StreamPacket packet){
byte[] payload = packet.getPayload(); byte[] payload = packet.getPayload();
int messageVersionNo = payload[0]; int messageVersionNo = payload[0];
@@ -244,8 +285,12 @@ public class StreamParser extends Thread{
String message = new String(Arrays.copyOfRange(payload,3,3 + length)); 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[] payload = packet.getPayload();
byte deviceType = payload[15]; byte deviceType = payload[15];
byte[] seqBytes = Arrays.copyOfRange(payload,11,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[] lonBytes = Arrays.copyOfRange(payload,20,24);
byte[] boatIdBytes = Arrays.copyOfRange(payload,7,11); byte[] boatIdBytes = Arrays.copyOfRange(payload,7,11);
byte[] headingBytes = Arrays.copyOfRange(payload,28,30); byte[] headingBytes = Arrays.copyOfRange(payload,28,30);
byte[] groundSpeedBytes = Arrays.copyOfRange(payload,38,40);
long timeStamp = extractTimeStamp(Arrays.copyOfRange(payload,1,7), 6); long timeStamp = extractTimeStamp(Arrays.copyOfRange(payload,1,7), 6);
// int boatSeq = ByteBuffer.wrap(seqBytes).getInt(); // int boatSeq = ByteBuffer.wrap(seqBytes).getInt();
long seq = bytesToLong(seqBytes); long seq = bytesToLong(seqBytes);
@@ -260,7 +307,9 @@ public class StreamParser extends Thread{
long lat = bytesToLong(latBytes); long lat = bytesToLong(latBytes);
long lon = bytesToLong(lonBytes); long lon = bytesToLong(lonBytes);
long heading = bytesToLong(headingBytes); 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){ if ((int)deviceType == 1 || (int)deviceType == 4){
// System.out.println("boatId = " + boatId); // System.out.println("boatId = " + boatId);
// System.out.println("deviceType = " + (long)deviceType); // System.out.println("deviceType = " + (long)deviceType);
@@ -268,13 +317,16 @@ public class StreamParser extends Thread{
//needs to be validated //needs to be validated
Point3D point = new Point3D(((180d * (double)lat)/Math.pow(2,31)),((180d *(double)lon)/Math.pow(2,31)),(double)heading); Point3D point = new Point3D(((180d * (double)lat)/Math.pow(2,31)),((180d *(double)lon)/Math.pow(2,31)),(double)heading);
boatPositions.putIfAbsent(boatId, point); boatPositions.putIfAbsent(boatId, point);
boatSpeeds.putIfAbsent(boatId, groundSpeed);
boatPositions.replace(boatId, point); boatPositions.replace(boatId, point);
boatSpeeds.replace(boatId, groundSpeed);
// System.out.println("lon = " + ((180d * (double)lon)/Math.pow(2,31))); // System.out.println("lon = " + ((180d * (double)lon)/Math.pow(2,31)));
// System.out.println("lat = " + ((180d *(double)lat)/Math.pow(2,31))); // System.out.println("lat = " + ((180d *(double)lat)/Math.pow(2,31)));
} }
} }
private static void extractMarkRounding(StreamPacket packet){ private static void extractMarkRounding(StreamPacket packet){
byte[] payload = packet.getPayload(); byte[] payload = packet.getPayload();
int messageVersionNo = payload[0]; int messageVersionNo = payload[0];
+6 -6
View File
@@ -4,37 +4,37 @@
<team> <team>
<name>Oracle Team USA</name> <name>Oracle Team USA</name>
<alias>USA</alias> <alias>USA</alias>
<velocity>12.9</velocity> <velocity>0.0</velocity>
<id>102</id> <id>102</id>
</team> </team>
<team> <team>
<name>Artemis Racing</name> <name>Artemis Racing</name>
<alias>ART</alias> <alias>ART</alias>
<velocity>13.1</velocity> <velocity>0.0</velocity>
<id>101</id> <id>101</id>
</team> </team>
<team> <team>
<name>Emirates Team New Zealand</name> <name>Emirates Team New Zealand</name>
<alias>NZL</alias> <alias>NZL</alias>
<velocity>15.6</velocity> <velocity>0.0</velocity>
<id>103</id> <id>103</id>
</team> </team>
<team> <team>
<name>Land Rover BAR</name> <name>Land Rover BAR</name>
<alias>BAR</alias> <alias>BAR</alias>
<velocity>13.3</velocity> <velocity>0.0</velocity>
<id>104</id> <id>104</id>
</team> </team>
<team> <team>
<name>SoftBank Team Japan</name> <name>SoftBank Team Japan</name>
<alias>JAP</alias> <alias>JAP</alias>
<velocity>14.7</velocity> <velocity>0.0</velocity>
<id>105</id> <id>105</id>
</team> </team>
<team> <team>
<name>Groupama Team France</name> <name>Groupama Team France</name>
<alias>FRC</alias> <alias>FRC</alias>
<velocity>11.4</velocity> <velocity>0.0</velocity>
<id>106</id> <id>106</id>
</team> </team>
</teams> </teams>
+11 -8
View File
@@ -1,15 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.text.*?> <?import javafx.scene.control.CheckBox?>
<?import javafx.scene.shape.*?> <?import javafx.scene.control.Label?>
<?import javafx.scene.control.*?>
<?import java.lang.*?>
<?import javafx.scene.canvas.*?>
<?import javafx.scene.layout.*?>
<?import javafx.scene.canvas.Canvas?>
<?import javafx.scene.layout.AnchorPane?> <?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.layout.ColumnConstraints?>
<?import javafx.scene.layout.GridPane?>
<?import javafx.scene.layout.Pane?>
<?import javafx.scene.layout.RowConstraints?>
<?import javafx.scene.layout.VBox?>
<?import javafx.scene.shape.Circle?>
<?import javafx.scene.text.Font?>
<?import javafx.scene.text.Text?>
<GridPane prefHeight="1080.0" prefWidth="1920.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="seng302.controllers.RaceViewController"> <GridPane prefHeight="1080.0" prefWidth="1920.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0" xmlns="http://javafx.com/javafx/8.0.112" xmlns:fx="http://javafx.com/fxml/1" fx:controller="seng302.controllers.RaceViewController">
<columnConstraints> <columnConstraints>
<ColumnConstraints maxWidth="246.0" minWidth="246.0" prefWidth="246.0" /> <ColumnConstraints maxWidth="246.0" minWidth="246.0" prefWidth="246.0" />
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="1034.0" /> <ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="1034.0" />