Stripped back codebase to make to create basic model for streaming data

Removed many classes involved with visualisation such as controllers and multiple fxmls. Now there is just one for debugging

Merged in Boat updating pattern from team 27

#story[828]
This commit is contained in:
William Muir
2017-04-08 17:49:50 +12:00
parent 9817fc9093
commit 34872a822b
26 changed files with 518 additions and 1625 deletions
@@ -1,283 +0,0 @@
package seng302.controllers;
import javafx.animation.*;
import javafx.fxml.FXML;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.layout.AnchorPane;
import javafx.scene.paint.Color;
import javafx.scene.text.Font;
import seng302.models.Boat;
import seng302.models.TimelineInfo;
import seng302.models.mark.GateMark;
import seng302.models.mark.Mark;
import seng302.models.mark.MarkType;
import seng302.models.mark.SingleMark;
import java.util.*;
/**
* Created by ptg19 on 15/03/17.
* Modified by Haoming Yin (hyi25) on 20/3/2017.
*/
public class CanvasController {
@FXML
private AnchorPane canvasPane;
private RaceViewController raceViewController;
private ResizableCanvas canvas;
private GraphicsContext gc;
private final double ORIGIN_LAT = 32.321504;
private final double ORIGIN_LON = -64.857063;
private final int SCALE = 16000;
public void setup(RaceViewController raceViewController){
this.raceViewController = raceViewController;
}
public void initialize() {
canvas = new ResizableCanvas();
canvasPane.getChildren().add(canvas);
// Bind canvas size to stack pane size.
canvas.widthProperty().bind(canvasPane.widthProperty());
canvas.heightProperty().bind(canvasPane.heightProperty());
gc = canvas.getGraphicsContext2D();
// 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;
@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);
// 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();
}
class ResizableCanvas extends Canvas {
public ResizableCanvas() {
// Redraw canvas when size changes.
widthProperty().addListener(evt -> draw());
heightProperty().addListener(evt -> draw());
}
private void draw() {
double width = getWidth();
double height = getHeight();
GraphicsContext gc = getGraphicsContext2D();
gc.clearRect(0, 0, width, height);
}
@Override
public boolean isResizable() {
return true;
}
@Override
public double prefWidth(double height) {
return getWidth();
}
@Override
public double prefHeight(double width) {
return getHeight();
}
}
private void drawFps(int fps){
if (raceViewController.isDisplayFps()){
gc.setFill(Color.BLACK);
gc.setFont(new Font(14));
gc.setLineWidth(3);
gc.fillText(fps + " FPS", 5, 20);
}
}
/**
* Draws all the boats.
*/
private void drawBoats() {
Map<Boat, TimelineInfo> timelineInfos = raceViewController.getTimelineInfos();
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.
*/
class Point {
double x, y;
Point (double x, double y) {
this.x = x;
this.y = y;
}
void rotate(double angle) {
double oldX = x;
double oldY = y;
this.x = oldX * Math.cos(angle) - oldY * Math.sin(angle);
this.y = oldX * Math.sin(angle) + oldY * Math.cos(angle);
}
}
/**
* Draws the course.
*/
private void drawCourse() {
for (Mark mark : raceViewController.getRace().getCourse()) {
if (mark.getMarkType() == MarkType.SINGLE_MARK) {
drawSingleMark((SingleMark) mark, Color.BLACK);
} else if (mark.getMarkType() == MarkType.GATE_MARK) {
drawGateMark((GateMark) mark);
}
}
}
/**
* Draw a given mark on canvas
*
* @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);
}
/**
* Draw a gate mark which contains two single marks
*
* @param gateMark
*/
private void drawGateMark(GateMark gateMark) {
Color color = Color.BLUE;
if (gateMark.getName().equals("Start")){
color = Color.RED;
}
if (gateMark.getName().equals("Finish")){
color = Color.GREEN;
}
drawSingleMark(gateMark.getSingleMark1(), color);
drawSingleMark(gateMark.getSingleMark2(), color);
GraphicsContext gc = canvas.getGraphicsContext2D();
gc.setStroke(color);
// Convert lat/lon to x,y
double x1 = (gateMark.getSingleMark1().getLongitude()- ORIGIN_LON) * SCALE;
double y1 = (ORIGIN_LAT - gateMark.getSingleMark1().getLatitude()) * SCALE;
double x2 = (gateMark.getSingleMark2().getLongitude() - ORIGIN_LON) * SCALE;
double y2 = (ORIGIN_LAT - gateMark.getSingleMark2().getLatitude()) * SCALE;
gc.setLineWidth(1);
gc.strokeLine(x1, y1, x2, y2);
}
}
+159 -21
View File
@@ -1,38 +1,176 @@
package seng302.controllers; package seng302.controllers;
import javafx.fxml.FXML; import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.fxml.Initializable; import javafx.fxml.Initializable;
import javafx.scene.layout.AnchorPane; import javafx.scene.Group;
import javafx.scene.layout.Pane; import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.control.Button;
import javafx.scene.paint.Color;
import seng302.models.Boat;
import seng302.models.Course;
import seng302.models.Race;
import seng302.models.mark.Mark;
import seng302.models.mark.MarkType;
import seng302.models.parsers.ConfigParser;
import seng302.models.parsers.CourseParser;
import seng302.models.parsers.TeamsParser;
import java.io.IOException;
import java.net.URL; import java.net.URL;
import java.util.ArrayList;
import java.util.ResourceBundle; import java.util.ResourceBundle;
/** /**
* Created by michaelrausch on 21/03/17. * Controller for main window for RaceVision
* Created by Kusal on 3/22/2017.
*/ */
public class Controller implements Initializable { public class Controller implements Initializable {
@FXML
private AnchorPane contentPane;
private void setContentPane(String jfxUrl){ private static final int MARKER_WIDTH = 10;
try{ private static final int MARKER_HEIGHT = 10;
contentPane.getChildren().removeAll(); private static final double ORIGINAL_LAT = 32.321504;
contentPane.getChildren().clear(); private static final double ORIGINAL_LON = -64.857063;
contentPane.getChildren().addAll((Pane) FXMLLoader.load(getClass().getResource(jfxUrl)));
}
catch(javafx.fxml.LoadException e){ @FXML
System.err.println(e.getCause()); private Canvas courseCanvas;
}
catch(IOException e){ @FXML
System.err.println(e); private Group boatGroup;
}
} @FXML
private Button playPauseButton;
private Course thisCourse;
private ArrayList<Boat> startingBoats;
private int raceDuration;
private Race race;
private boolean raceRunning = false;
private CourseParser cp;
private TeamsParser tp;
private ConfigParser cop;
@Override @Override
public void initialize(URL location, ResourceBundle resources) { public void initialize(URL location, ResourceBundle resources) {
setContentPane("/views/RaceView.fxml"); cp = new CourseParser("/config/course.xml");
tp = new TeamsParser("/config/teams.xml");
cop = new ConfigParser("/config/config.xml");
thisCourse = new Course(cp.getCourse());
startingBoats = tp.getBoats();
race = new Race(thisCourse.getMarks(), startingBoats, cop.getTimeScale(), this);
init();
}
/**
* Initialises a race on the screen after it has been loaded
*/
private void init() {
initMap();
initBoats();
playPauseButton.setDisable(false);
}
/**
* Initialise the map by drawing it onto courseCanvas.
*/
private void initMap() {
//Create the boundary of the course displayed on the map
drawCourse();
}
/**
* Draw the markers and gates onto the courseCanvas.
*/
private void drawCourse() {
GraphicsContext gc = courseCanvas.getGraphicsContext2D();
gc.save();
for(Mark rp : thisCourse.getMarks()) {
if (rp.getMarkType().equals(MarkType.SINGLE_MARK)) {
gc.setFill(Color.GRAY);
gc.fillOval(convertLongToX(rp.getLongitude()), convertLatToY(rp.getLatitude()), MARKER_WIDTH, MARKER_HEIGHT);
} else if (rp.getMarkType().equals(MarkType.GATE_MARK)) {
gc.setFill(Color.GRAY);
gc.fillOval(convertLongToX(rp.getLongitude()), convertLatToY(rp.getLatitude()), MARKER_WIDTH, MARKER_HEIGHT);
// gc.fillOval(((OpenGate) rp).getDrawX2(), ((OpenGate) rp).getDrawY2(), MARKER_WIDTH, MARKER_HEIGHT);
gc.setLineWidth(2);
gc.setFill(Color.GREEN);
gc.setStroke(Color.GREEN);
}
gc.fillOval(convertLongToX(rp.getLongitude()), convertLatToY(rp.getLatitude()), MARKER_WIDTH, MARKER_HEIGHT);
// gc.fillOval(((ClosedGate) rp).getDrawX2(), ((ClosedGate) rp).getDrawY2(), MARKER_WIDTH, MARKER_HEIGHT);
// gc.strokeLine(convertLongToX(rp.getLongitude()) + 5, convertLatToY(rp.getLatitude()) + 5, ((ClosedGate) rp).getDrawX2() + 5, ((ClosedGate) rp).getDrawY2() + 5);
}
gc.restore();
}
/**
* Places boats at starting line
*/
private void initBoats() {
// int startingX = (convertLongToX(thisCourse.getMarks().get(0).getLongitude())).intValue();
// int startingY = (convertLatToY(thisCourse.getMarks().get(0).getLongitude())).intValue();
int startingX = 50;
int startingY = 100;
for(Boat boat : startingBoats) {
boat.moveBoatTo(startingX, startingY);
boatGroup.getChildren().add(boat.getBoatObject());
boat.setCurrentLeg(race.getRaceLegs().get(0));
boat.setHeading(race.getRaceLegs().get(0).getHeading());
boat.setLegDistance(0d);
}
}
/**
* Starts and stops the race depending on whether or not it is already running
*/
public void playPause() {
if (!raceRunning) {
play();
} else {
pause();
}
}
/**
* Plays the race and updates the play / pause button
*/
private void play() {
race.run();
raceRunning = true;
playPauseButton.setText("Pause");
}
/**
* Pauses the race and updates the play / pause button
*/
private void pause() {
race.pause();
raceRunning = false;
playPauseButton.setText("Play");
}
private Double convertLongToX(Double lon) {
return (lon - ORIGINAL_LON) * thisCourse.getDistanceScaleFactor();
}
private Double convertLatToY(Double lat) {
return (ORIGINAL_LAT - lat) * thisCourse.getDistanceScaleFactor();
}
public Button getPlayPauseButton() {
return playPauseButton;
} }
} }
@@ -1,80 +0,0 @@
package seng302.controllers;
import seng302.models.Boat;
import seng302.models.Race;
import seng302.models.parsers.ConfigParser;
import seng302.models.parsers.CourseParser;
import seng302.models.parsers.TeamsParser;
import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Random;
/**
* Created by zyt10 on 17/03/17.
* run before CanvasController to initialize race events
* the CanvasController then uses the event data to make the animations
*/
public class RaceController {
Race race = null;
public void initializeRace() {
String raceConfigFile = "/config/config.xml";
String teamsConfigFile = "/config/teams.xml";
try {
race = createRace(raceConfigFile, teamsConfigFile);
} catch (Exception e) {
System.out.println("There was an error creating the race.");
}
if (race != null) {
race.startRace();
} else {
System.out.println("There was an error creating the race. Exiting.");
}
}
public Race createRace(String configFile, String teamsConfigFile) throws Exception {
Race race = new Race();
// Read team names from file
TeamsParser tp = new TeamsParser(teamsConfigFile);
// Read course from file
ConfigParser config = new ConfigParser(configFile);
ArrayList<String> boatNames = new ArrayList<>();
ArrayList<Boat> teams = tp.getBoats();
//get race size
int numberOfBoats = teams.size();
//get time scale
double timeScale = config.getTimeScale();
race.setTimeScale(timeScale);
for (Boat boat : teams) {
boatNames.add(boat.getTeamName());
race.addBoat(boat);
}
// Shuffle team names
long seed = System.nanoTime();
Collections.shuffle(boatNames, new Random(seed));
if (numberOfBoats > Array.getLength(boatNames.toArray())) {
return null;
}
CourseParser course = new CourseParser("/config/course.xml");
race.addCourse(course.getCourse());
return race;
}
public Race getRace() {
return race;
}
}
@@ -1,37 +0,0 @@
package seng302.controllers;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.VBox;
import javafx.scene.text.Text;
import seng302.models.Race;
import java.net.URL;
import java.util.ResourceBundle;
/**
* Created by ptg19 on 20/03/17.
*/
public class RaceResultController implements Initializable{
@FXML private AnchorPane window;
@FXML private VBox resultsVBox;
private Race race;
RaceResultController(Race race){
this.race = race;
}
@Override
public void initialize(URL location, ResourceBundle resources) {
int boatPosition = this.race.getFinishedBoats().length;
for (int i = this.race.getFinishedBoats().length - 1; i >= 0; i--){
resultsVBox.getChildren().add(0, new Text(boatPosition + ": " + this.race.getFinishedBoats()[i].getTeamName()));
boatPosition--;
}
}
}
@@ -1,280 +0,0 @@
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;
import javafx.fxml.FXMLLoader;
import javafx.scene.control.CheckBox;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.Pane;
import javafx.scene.layout.VBox;
import javafx.scene.text.Text;
import javafx.util.Duration;
import seng302.models.Boat;
import seng302.models.Event;
import seng302.models.Race;
import seng302.models.TimelineInfo;
import seng302.models.parsers.ConfigParser;
import java.io.IOException;
import java.util.*;
/**
* Created by ptg19 on 29/03/17.
*/
public class RaceViewController {
@FXML
private VBox positionVbox;
@FXML
private CheckBox toggleAnnotation, toggleFps;
@FXML
private Text timerLabel;
@FXML
private AnchorPane contentAnchorPane;
@FXML
private Text windArrowText, windDirectionText;
@FXML
private CanvasController includedCanvasController;
private boolean displayAnnotations;
private boolean displayFps;
private Timeline timerTimeline;
private Map<Boat, TimelineInfo> timelineInfos = new HashMap<>();
private ArrayList<Boat> boatOrder = new ArrayList<>();
private Race race;
public void initialize() {
includedCanvasController.setup(this);
RaceController raceController = new RaceController();
raceController.initializeRace();
race = raceController.getRace();
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(){
displayAnnotations = true;
displayFps = true;
toggleAnnotation.selectedProperty().addListener(new ChangeListener<Boolean>() {
@Override
public void changed(ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) {
displayAnnotations = !displayAnnotations;
}
});
toggleFps.selectedProperty().addListener(new ChangeListener<Boolean>() {
@Override
public void changed(ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) {
displayFps = !displayFps;
}
});
}
private void initializeTimer(){
timerTimeline = new Timeline();
timerTimeline.setCycleCount(Timeline.INDEFINITE);
// Run timer update every second
timerTimeline.getKeyFrames().add(
new KeyFrame(Duration.seconds(1),
event -> {
// Stop timer if race is finished
if (this.race.isRaceFinished()) {
this.timerTimeline.stop();
} else {
timerLabel.setText(convertTimeToMinutesSeconds(race.getRaceTime()));
this.race.incrementRaceTime();
}
})
);
// Start the timer
timerTimeline.playFromStart();
}
/**
* Generates time line for each boat, and stores time time into timelineInfos hash map
*/
private void initializeTimelines() {
HashMap<Boat, List> 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<KeyFrame> keyFrames = new ArrayList<>();
List<Event> 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();
}
private void setRaceDuration(){
Double maxDuration = 0.0;
Timeline maxTimeline = null;
for (TimelineInfo timelineInfo : timelineInfos.values()) {
Timeline timeline = timelineInfo.getTimeline();
if (timeline.getTotalDuration().toMillis() >= maxDuration) {
maxDuration = timeline.getTotalDuration().toMillis();
maxTimeline = timeline;
}
// Timelines are paused by default
timeline.play();
timeline.pause();
}
maxTimeline.setOnFinished(event -> {
race.setRaceFinished();
loadRaceResultView();
});
}
/**
* Play each boats timerTimeline
*/
public void playTimelines(){
for (TimelineInfo timelineInfo : timelineInfos.values()){
Timeline timeline = timelineInfo.getTimeline();
if (timeline.getStatus() == Animation.Status.PAUSED){
timeline.play();
}
}
}
/**
* Pause each boats timerTimeline
*/
public void pauseTimelines(){
for (TimelineInfo timelineInfo : timelineInfos.values()){
Timeline timeline = timelineInfo.getTimeline();
if (timeline.getStatus() == Animation.Status.RUNNING){
timeline.pause();
}
}
}
/**
* Display the list of boats in the order they finished the race
*/
private void loadRaceResultView() {
FXMLLoader loader = new FXMLLoader(getClass().getResource("/views/FinishView.fxml"));
loader.setController(new RaceResultController(race));
try {
contentAnchorPane.getChildren().removeAll();
contentAnchorPane.getChildren().clear();
contentAnchorPane.getChildren().addAll((Pane) loader.load());
} catch (javafx.fxml.LoadException e) {
System.err.println(e.getCause());
} catch (IOException e) {
System.err.println(e);
}
}
public void handleEvent(Event event) {
Boat boat = event.getBoat();
boatOrder.remove(boat);
boat.setMarkLastPast(event.getMarkPosInRace());
boatOrder.add(boat);
boatOrder.sort(new Comparator<Boat>() {
@Override
public int compare(Boat b1, Boat b2) {
return b2.getMarkLastPast() - b1.getMarkLastPast();
}
});
showOrder();
}
private void showOrder() {
positionVbox.getChildren().clear();
positionVbox.getChildren().removeAll();
for (Boat boat : boatOrder) {
positionVbox.getChildren().add(new Text(boat.getShortName() + " " + boat.getSpeedInKnots() + " Knots"));
}
}
/**
* Convert seconds to a string of the format mm:ss
*
* @param time the time in seconds
* @return a formatted string
*/
public String convertTimeToMinutesSeconds(int time) {
if (time < 0) {
return String.format("-%02d:%02d", (time * -1) / 60, (time * -1) % 60);
}
return String.format("%02d:%02d", time / 60, time % 60);
}
public void stopTimer() {
timerTimeline.stop();
}
public void startTimer() {
timerTimeline.play();
}
public boolean isDisplayFps() {
return displayFps;
}
public boolean isDisplayAnnotations() {
return displayAnnotations;
}
public Race getRace() {
return race;
}
public Map<Boat, TimelineInfo> getTimelineInfos() {
return timelineInfos;
}
}
+59 -15
View File
@@ -1,28 +1,36 @@
package seng302.models; package seng302.models;
import javafx.scene.paint.Color; import javafx.scene.paint.Color;
import javafx.scene.shape.Polygon;
import javafx.scene.text.Text;
/** /**
* Represents a boat in the race. * Represents a boat in the race.
*/ */
public class Boat { public class Boat {
private static final double BOAT_HEIGHT = 15d;
private static final double BOAT_WIDTH = 10d;
private static final double VELOCITY_WAKE_RATIO = 1/2d; //Ratio for deciding how long the wake will be wrt velocity
private String teamName; // The name of the team, this is also the name of the boat private String teamName; // The name of the team, this is also the name of the boat
private double velocity; // In meters/second private Double velocity; // In meters/second
private double lat; // Boats position private Double lat; // Boats position
private double lon; // - private Double lon; // -
private double distanceToNextMark; private Double legDistance;
private Color color; private Color color;
private int markLastPast; private Leg currentLeg;
private double heading; private Double heading;
private String shortName; private String shortName;
private Polygon boatObject = new Polygon();
public Boat(String teamName) { public Boat(String teamName) {
this.teamName = teamName; this.teamName = teamName;
this.velocity = 10; // Default velocity this.velocity = 10d; // Default velocity
this.lat = 0.0; this.lat = 0.0;
this.lon = 0.0; this.lon = 0.0;
this.distanceToNextMark = 0.0; this.legDistance = 0.0;
this.shortName = ""; this.shortName = "";
} }
@@ -36,9 +44,12 @@ public class Boat {
public Boat(String teamName, double boatVelocity, String shortName) { public Boat(String teamName, double boatVelocity, String shortName) {
this.teamName = teamName; this.teamName = teamName;
this.velocity = boatVelocity; this.velocity = boatVelocity;
this.distanceToNextMark = 0.0; this.legDistance = 0.0;
this.color = Colors.getColor(); this.color = Colors.getColor();
this.shortName = shortName; this.shortName = shortName;
this.boatObject.getPoints().addAll(BOAT_WIDTH /2,0.0,
BOAT_WIDTH, BOAT_HEIGHT,
0.0, BOAT_HEIGHT);
} }
/** /**
@@ -88,8 +99,12 @@ public class Boat {
this.lon = lon; this.lon = lon;
} }
public void setDistanceToNextMark(double distance){ public Double getLegDistance() {
this.distanceToNextMark = distance; return legDistance;
}
public void setLegDistance(double legDistance){
this.legDistance = legDistance;
} }
public double getLatitude(){ public double getLatitude(){
@@ -108,12 +123,12 @@ public class Boat {
return Math.round((this.velocity * 1.94384) * 100d) / 100d; return Math.round((this.velocity * 1.94384) * 100d) / 100d;
} }
public void setMarkLastPast(int markLastPast) { public Leg getCurrentLeg() {
this.markLastPast = markLastPast; return currentLeg;
} }
public int getMarkLastPast() { public void setCurrentLeg(Leg currentLeg) {
return markLastPast; this.currentLeg = currentLeg;
} }
public void setHeading(double heading){ public void setHeading(double heading){
@@ -127,4 +142,33 @@ public class Boat {
public String getShortName(){ public String getShortName(){
return this.shortName; 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());
}
/**
* 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(int x, int y) {
boatObject.setLayoutX(x);
boatObject.setLayoutY(y);
boatObject.relocate(boatObject.getLayoutX(), boatObject.getLayoutY());
}
public Polygon getBoatObject() {
return boatObject;
}
} }
+54
View File
@@ -0,0 +1,54 @@
package seng302.models;
/**
* Created by wmu16 on 4/8/17.
*/
import javafx.scene.canvas.Canvas;
import javafx.util.Pair;
import seng302.models.mark.Mark;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
/**
* Class for describing the course information for the canvas
* Created by wmu16 on 22/03/17.
*/
public class Course {
private ArrayList<Leg> Legs;
private ArrayList<Mark> marks;
public static final Integer SCALE = 160000;
public Course(ArrayList<Mark> marks) {
this.marks = marks;
this.Legs = makeLegs();
}
/**
* Makes the race legs out of all the marker marks for the course
* @return ArrayList of Legs
*/
private ArrayList<Leg> makeLegs() {
ArrayList<Leg> Legs = new ArrayList<>();
for (int i = 0; i < marks.size()-1; i++) {
Legs.add(new Leg(marks.get(i), marks.get(i+1)));
}
return Legs;
}
public ArrayList<Mark> getMarks() {
return marks;
}
public double getDistanceScaleFactor() {
return SCALE;
}
public ArrayList<Leg> getLegs() {
return Legs;
}
}
-148
View File
@@ -1,148 +0,0 @@
package seng302.models;
import seng302.models.mark.Mark;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
* Event class containing the time of specific event, related team/boat, and
* event location such as leg.
*/
public class Event {
private Double time; // Time the event occurs
private Boat boat;
private boolean isFinishingEvent = false; // This event occurs when a boat finishes the race
private Mark mark1; // This mark
private Mark mark2; // Next mark
private int markPosInRace; // the position of the current mark in the race course
private final double ORIGIN_LAT = 32.320504;
private final double ORIGIN_LON = -64.857063;
private final double SCALE = 16000;
/**
* Event class containing the time of specific event, related team/boat, and
* event location such as leg.
*
* @param eventTime, what time the event happens
* @param eventBoat, the boat that the event belongs to
*/
public Event(Double eventTime, Boat eventBoat, Mark mark1, Mark mark2, int markPosInRace) {
this.time = eventTime;
this.boat = eventBoat;
this.mark1 = mark1;
this.mark2 = mark2;
this.markPosInRace = markPosInRace;
}
/**
* Event class containing the time of specific event, related team/boat, and
* event location such as leg.
*
* @param eventTime, what time the event happens
* @param eventBoat, the boat that the event belongs to
*/
public Event(Double eventTime, Boat eventBoat, Mark mark1, int markPosInRace) {
this.time = eventTime;
this.boat = eventBoat;
this.mark1 = mark1;
this.markPosInRace = markPosInRace;
this.isFinishingEvent = true;
}
public double getTime() {
return this.time;
}
public void setTime(double eventTime) {
this.time = eventTime;
}
/**
* Gets the time in a formatted string
*
* @return the string of time
*/
public String getTimeString() {
return (new SimpleDateFormat("mm:ss:SSS")).format(new Date(time.longValue()));
}
public Boat getBoat() {
return this.boat;
}
public void setBoat(Boat eventBoat) {
this.boat = eventBoat;
}
public boolean getIsFinishingEvent() {
return this.isFinishingEvent;
}
/**
* Get a string that contains the timestamp and course information for this event
*
* @return A string that details what happened in this event
*/
public String getEventString() {
// This event is a boat finishing the race
if (this.isFinishingEvent) {
return (this.getTimeString() + ", " + this.getBoat().getTeamName() + " finished the race");
}
System.out.println(this.getDistanceBetweenMarks());
return (this.getTimeString() + ", " + this.getBoat().getTeamName() + " passed " + this.mark1.getName() + " going heading " + this.getBoatHeading() + "°");
}
/**
* @return the distance between the two marks
*/
public double getDistanceBetweenMarks() {
double earth_radius = 6378.137;
double dLat = this.mark2.getLatitude() * Math.PI / 180 - this.mark1.getLatitude() * Math.PI / 180;
double dLon = this.mark2.getLongitude() * Math.PI / 180 - this.mark1.getLongitude() * Math.PI / 180;
double a = Math.sin(dLat / 2) * Math.sin(dLat / 2) + Math.cos(this.mark1.getLatitude() * Math.PI / 180) * Math.sin(dLon / 2) * Math.sin(dLon / 2);
double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
double d = earth_radius * c;
return d * 1000;
}
/**
* Calculates current boat heading direction.
* @return the boats heading as degree. vertical upward is 0 degree, and degree goes up clockwise.
*/
public double getBoatHeading() {
if (mark2 == null){
return 0.0;
}
double x1 = (mark1.getLongitude() - ORIGIN_LON) * SCALE;
double y1 = (ORIGIN_LAT - mark1.getLatitude()) * SCALE;
double x2 = (mark2.getLongitude() - ORIGIN_LON) * SCALE;
double y2 = (ORIGIN_LAT - mark2.getLatitude()) * SCALE;
double headingRadians = Math.atan2(y2-y1, x2-x1);
if (headingRadians < 0){
headingRadians += 2 * Math.PI;
}
// Convert back to degrees, and flip 180 degrees
// return ((headingRadians) * 180) / Math.PI;
return (Math.toDegrees(headingRadians) + 90) % 360;
}
public Mark getThisMark() {
return this.mark1;
}
public int getMarkPosInRace() {
return markPosInRace;
}
}
+68 -87
View File
@@ -1,116 +1,97 @@
package seng302.models; package seng302.models;
import seng302.models.mark.SingleMark;
import seng302.models.mark.Mark;
/** /**
* Represents the leg of a race. * Class for defining the leg of a race between two markers
*/ * Created by cir27 on 3/03/17.
*/
public class Leg { public class Leg {
private int heading;
private int distance; private final double ORIGIN_LAT = 32.320504;
private boolean isFinishingLeg; private final double ORIGIN_LON = -64.857063;
private SingleMark startingSingleMark; private final double SCALE = 16000;
private Double distance;
private Double heading;
private Mark end;
private Mark start;
Leg(Mark start, Mark end) {
this.distance = calculateMarkerDistance(start, end);
this.heading = angleFromCoordinate(start, end);
this.start = start;
this.end = end;
}
/** /**
* Create a new leg * Calculates the euclidian distance between two markers on the canvas using xy coordinates
* *
* @param heading, the magnetic heading of this leg * @param geoPointOne first geographical point
* @param distance, the total distance of this leg in meters * @param geoPointTwo second geographical point
* @param singleMark, the singleMark this leg starts on * @return the distance between two points in meters
*/ */
public Leg(int heading, int distance, SingleMark singleMark) { private Double calculateMarkerDistance(Mark geoPointOne, Mark geoPointTwo) {
this.heading = heading;
this.distance = distance; double earth_radius = 6378.137;
this.startingSingleMark = singleMark; double dLat = geoPointTwo.getLatitude() * Math.PI / 180 - geoPointOne.getLatitude() * Math.PI / 180;
this.isFinishingLeg = false; double dLon = geoPointTwo.getLongitude() * Math.PI / 180 - geoPointOne.getLongitude() * Math.PI / 180;
double a = Math.sin(dLat / 2) * Math.sin(dLat / 2) + Math.cos(geoPointOne.getLatitude() * Math.PI / 180) * Math.sin(dLon / 2) * Math.sin(dLon / 2);
double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
double d = earth_radius * c;
return d * 1000;
} }
/** /**
* Create a new leg * Calculates the angle between to angular co-ordinates on a sphere.
* *
* @param heading, the magnetic heading of this leg * @param geoPointOne first geographical location
* @param distance, the total distance of this leg in meters * @param geoPointTwo second geographical location
* @param markerName, the name of the marker this leg starts on * @return the angle from point one to point two
*/ */
public Leg(int heading, int distance, String markerName) { private Double angleFromCoordinate(Mark geoPointOne, Mark geoPointTwo) {
this.heading = heading;
this.distance = distance; if (geoPointTwo == null){
this.startingSingleMark = new SingleMark(markerName); return 0.0;
this.isFinishingLeg = false;
} }
/** double x1 = (geoPointOne.getLongitude() - ORIGIN_LON) * SCALE;
* Get the heading of this leg double y1 = (ORIGIN_LAT - geoPointOne.getLatitude()) * SCALE;
* @return int double x2 = (geoPointTwo.getLongitude() - ORIGIN_LON) * SCALE;
*/ double y2 = (ORIGIN_LAT - geoPointTwo.getLatitude()) * SCALE;
public int getHeading() {
return this.heading; double headingRadians = Math.atan2(y2-y1, x2-x1);
if (headingRadians < 0){
headingRadians += 2 * Math.PI;
} }
/** // Convert back to degrees, and flip 180 degrees
* Set the heading for this leg // return ((headingRadians) * 180) / Math.PI;
* @param heading return (Math.toDegrees(headingRadians) + 90) % 360;
*/
public void setHeading(int heading) {
this.heading = heading;
} }
/** Double getDistance()
* Get the total distance of this leg in meters {
* @return int
*/
public int getDistance() {
return this.distance; return this.distance;
} }
/** Mark getEnd()
* Set the distance of this leg in meters {
* @param distance return this.end;
*/
public void setDistance(int distance) {
this.distance = distance;
} }
/** public Mark getStart() {
* Returns the marker this leg started on return start;
* @return SingleMark
*/
public SingleMark getMarker() {
return this.startingSingleMark;
} }
/** public Double getHeading()
* Set the singleMark this leg starts on {
* @param singleMark return this.heading;
*/
public void setMarker(SingleMark singleMark) {
this.startingSingleMark = singleMark;
}
/**
* Returns the name of the marker this leg started on
* @return String
*/
public String getMarkerLabel() {
return this.startingSingleMark.getName();
}
/**
* Specify whether or not the race finishes on this leg
*
* @param isFinishingLeg whether or not the race finishes on this leg
*/
public void setFinishingLeg(boolean isFinishingLeg) {
this.isFinishingLeg = isFinishingLeg;
}
/**
* Returns whether or not the race finishes after this leg
* @return true if this the race finishes after this leg
*/
public boolean getIsFinishingLeg() {
return this.isFinishingLeg;
} }
} }
+129 -90
View File
@@ -1,5 +1,7 @@
package seng302.models; package seng302.models;
import javafx.animation.AnimationTimer;
import seng302.controllers.Controller;
import seng302.models.mark.Mark; import seng302.models.mark.Mark;
import java.util.*; import java.util.*;
@@ -8,46 +10,136 @@ import java.util.*;
* Race class containing the boats and legs in the race * Race class containing the boats and legs in the race
* Created by mra106 on 8/3/2017. * Created by mra106 on 8/3/2017.
*/ */
public class Race { public class Race extends Thread {
private static final double UPDATE_TIME = 0.016666; // 1 / 60 ie 60fps
private ArrayList<Boat> boats; // The boats in the race private ArrayList<Boat> boats; // The boats in the race
private ArrayList<Boat> finishingOrder; // The order in which the boats finish the race private ArrayList<Boat> finishingOrder; // The order in which the boats finish the race
private HashMap<Boat, List> events = new HashMap<>(); // The events that occur in the race private List<Mark> markers; // Marks in the race
private List<Mark> course; // Marks in the race private List<Leg> raceLegs;
private long startTime = 0;
private double timeScale = 1;
private boolean raceFinished = false; // Race is finished private boolean raceFinished = false; // Race is finished
private int raceTime = -2; // Current time in the race private int raceTime = -2; // Current time in the race
private Double timeScale = null;
private boolean raceStarted = false;
private Controller controller;
/** /**
* Race class containing the boats and legs in the race * Race class containing the boats and legs in the race
*/ */
public Race() { public Race(List<Mark> markers, ArrayList<Boat> boats, Double timescale, Controller controller) {
this.boats = new ArrayList<>(); this.boats = boats;
this.markers = markers;
this.raceLegs = makeRaceLegs();
this.controller = controller;
this.timeScale = timescale;
this.finishingOrder = new ArrayList<>(); this.finishingOrder = new ArrayList<>();
this.course = new ArrayList<>(); }
/**
* Makes the race legs out of all the marker points for the course
* @return ArrayList of raceLegs
*/
private ArrayList<Leg> makeRaceLegs() {
ArrayList<Leg> raceLegs = new ArrayList<>();
for (int i=0; i < markers.size()-1; i++) {
raceLegs.add(new Leg(markers.get(i), markers.get(i+1)));
}
return raceLegs;
}
/**
* A timer that is called every frame to call the action of moving the boats
*/
private AnimationTimer at = new AnimationTimer() {
@Override
public void handle(long now) {
//Updating Boat positions
if(finishingOrder.size() != boats.size()) {
// update the time
boats.stream().filter(boat -> !finishingOrder.contains(boat)).forEach(boat -> {
updateBoatPosition(boat);
});
} else {
at.stop();
}
}
};
/**
* Begins the race simulation
*/
@Override
public void run() {
if (!raceStarted) {
// controller.getPlayPauseButton().setDisable(true);
at.start();
}
}
/**
* Moves the coordinates of the boats.
* @param boat The boat to update the position of
*/
private void updateBoatPosition(Boat boat) {
Double thisHeading = boat.getHeading();
// TODO: 4/8/17 wmu16 - Add a distance scale factor from lat long distance in Metres to xy equivalent
Double hypMove = boat.getVelocity() * UPDATE_TIME * timeScale; //* distanceScaleFactor
boat.setLegDistance(boat.getLegDistance() + hypMove);
moveBoat(boat, thisHeading, hypMove);
} }
/** /**
* Add a boat to the race * Moves a boat along coordinates by breaking down the distance moved along the hypotenuse into x and y components
* * @param boat The Boat to move
* @param boat, the boat to add * @param heading The heading the boat is moving at
* @param hypMove The distance moved along the hypotenuse (absolute distance)
*/ */
public void addBoat(Boat boat) { private void moveBoat(Boat boat, Double heading, Double hypMove) {
boats.add(boat); //If the boat has not yet passed the marker at the end of the leg increase its (x,y) as per normal
// TODO: 4/8/17 wmu16 - Initialising boat positions and legs and headings etc.
if(boat.getLegDistance() <= boat.getCurrentLeg().getDistance()) {
Double xMove = hypMove * Math.sin(Math.toRadians(heading));
Double yMove = - hypMove * Math.cos(Math.toRadians(heading));
boat.moveBoatBy(xMove, yMove);
//If the boat has finished...
} else if (getNextRaceLeg(boat.getCurrentLeg()).equals(boat.getCurrentLeg())) {
finishingOrder.add(boat);
//Otherwise apply the overflow distance of the leg to the next leg
} else {
Double overflowDistance = boat.getLegDistance() - boat.getCurrentLeg().getDistance();
boat.setCurrentLeg(getNextRaceLeg(boat.getCurrentLeg()));
boat.setHeading(boat.getCurrentLeg().getHeading());
boat.setLegDistance(overflowDistance);
moveBoat(boat, boat.getHeading(), overflowDistance);
}
} }
/** /**
* Returns a list of boats in a random order * Returns the next leg in the race
* * @param currentRaceLeg The leg that you are currently on
* @return a list of boats * @return The next race leg or the same race leg if it has reached the end
*/ */
public Boat[] getShuffledBoats() { private Leg getNextRaceLeg(Leg currentRaceLeg) {
// Shuffle the list of boats Leg nextRaceLeg = currentRaceLeg;
long seed = System.nanoTime(); for(int i = 0; i< raceLegs.size() - 1; i++) {
Collections.shuffle(this.boats, new Random(seed)); if (raceLegs.get(i).equals(currentRaceLeg)) {
nextRaceLeg = raceLegs.get(i + 1);
return boats.toArray(new Boat[boats.size()]);
} }
}
return nextRaceLeg;
}
/** /**
* Returns a list of boats in the order that they * Returns a list of boats in the order that they
@@ -69,85 +161,28 @@ public class Race {
return boats.toArray(new Boat[boats.size()]); return boats.toArray(new Boat[boats.size()]);
} }
/**
* Sets time scale
*
* @param timeScale
*/
public void setTimeScale(double timeScale) {
this.timeScale = timeScale;
}
/**
* Generate all events that will happen during the race.
*/
private void generateEvents() {
for (Boat boat : this.boats) {
double totalDistance = 0;
int numberOfMarks = this.course.size();
for (int i = 0; i < numberOfMarks; i++) {
Double time = (totalDistance / boat.getVelocity() / timeScale);
// If there are singleMarks after this event
if (i < numberOfMarks - 1) {
Event event = new Event(time, boat, course.get(i), course.get(i + 1), i);
try {
events.get(boat).add(event);
} catch (NullPointerException e) {
events.put(boat, new ArrayList<>(Arrays.asList(event)));
}
totalDistance += event.getDistanceBetweenMarks();
System.out.println(totalDistance);
System.out.println(boat.getVelocity());
}
// There are no more marks after this event
else{
Event event = new Event(time, boat, course.get(i), i);
events.get(boat).add(event);
}
}
}
}
/** /**
* Starts a race and generates all events for the race. * Starts a race and generates all events for the race.
*/ */
public void startRace() { public void startRace() {
// record start time. // record start time.
this.startTime = System.currentTimeMillis();
generateEvents();
}
public void pause() {
at.stop();
} }
/** /**
* Set the race course * Get a list of marks in the markers
* @param course a list of marks in the course
*/
public void addCourse(List<Mark> course) {
this.course = course;
}
/**
* Get a list of marks in the course
* @return * @return
*/ */
public List<Mark> getCourse() { public List<Mark> getMarkers() {
return course; return markers;
} }
/**
* Get a map of the events in the race
* @return
*/
public HashMap<Boat, List> getEvents() {
return events;
}
/** /**
* Set a boat as finished * Set a boat as finished
@@ -191,7 +226,11 @@ public class Race {
/** /**
* Increment the race time by one second * Increment the race time by one second
*/ */
public void incrementRaceTime(){ public void incrementRaceTime() {
this.raceTime += this.timeScale; this.raceTime += this.timeScale;
} }
public List<Leg> getRaceLegs() {
return raceLegs;
}
} }
@@ -1,31 +0,0 @@
package seng302.models;
import javafx.animation.Timeline;
import javafx.beans.property.DoubleProperty;
/**
* Created by zyt10 on 17/03/17.
* this class is literally just to associate a timeline with a DoubleProperty x and y
*/
public class TimelineInfo {
private Timeline timeline;
private DoubleProperty x;
private DoubleProperty y;
public TimelineInfo(Timeline timeline, DoubleProperty x, DoubleProperty y) {
this.timeline = timeline;
this.x = x;
this.y = y;
}
public Timeline getTimeline() {
return timeline;
}
public DoubleProperty getX() {
return x;
}
public DoubleProperty getY() {
return y;
}
}
+2 -2
View File
@@ -1,6 +1,6 @@
<?xml version="1.0" ?> <?xml version="1.0" ?>
<course> <markers>
<marks> <marks>
<gate> <gate>
<name type="start-line">Start</name> <name type="start-line">Start</name>
@@ -68,4 +68,4 @@
<five>Leeward Gate</five> <five>Leeward Gate</five>
<six>Finish</six> <six>Finish</six>
</order> </order>
</course> </markers>
-7
View File
@@ -1,7 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import java.lang.*?>
<?import javafx.scene.layout.*?>
<AnchorPane fx:id="canvasPane" prefHeight="960.0" prefWidth="1280.0" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="seng302.controllers.CanvasController" />
-55
View File
@@ -1,55 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.geometry.*?>
<?import javafx.scene.text.*?>
<?import javafx.scene.control.*?>
<?import java.lang.*?>
<?import javafx.scene.layout.*?>
<AnchorPane fx:id="raceResults" maxHeight="1080.0" maxWidth="1920.0" minHeight="1080.0" minWidth="1920.0" prefHeight="1080.0" prefWidth="1920.0" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1">
<children>
<GridPane layoutX="444.0" layoutY="256.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
<columnConstraints>
<ColumnConstraints hgrow="SOMETIMES" maxWidth="620.1734008789062" minWidth="10.0" prefWidth="493.2829895019531" />
<ColumnConstraints hgrow="SOMETIMES" maxWidth="862.0581665039062" minWidth="10.0" prefWidth="786.7170104980469" />
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="100.0" />
</columnConstraints>
<rowConstraints>
<RowConstraints maxHeight="348.0" minHeight="10.0" prefHeight="112.0" vgrow="SOMETIMES" />
<RowConstraints maxHeight="348.0" minHeight="10.0" prefHeight="99.0" vgrow="SOMETIMES" />
<RowConstraints maxHeight="348.0" minHeight="7.0" prefHeight="88.0" vgrow="SOMETIMES" />
<RowConstraints maxHeight="348.0" minHeight="0.0" prefHeight="278.3376770019531" vgrow="SOMETIMES" />
<RowConstraints maxHeight="653.0" minHeight="0.0" prefHeight="98.66232299804688" vgrow="SOMETIMES" />
<RowConstraints maxHeight="812.0" minHeight="10.0" prefHeight="418.4577941894531" vgrow="SOMETIMES" />
</rowConstraints>
<children>
<Text strokeType="OUTSIDE" strokeWidth="0.0" text="Race Results:" wrappingWidth="616.5260620117188" GridPane.rowIndex="2">
<font>
<Font size="45.0" />
</font>
<GridPane.margin>
<Insets left="50.0" />
</GridPane.margin>
</Text>
<VBox fx:id="resultsVBox" prefHeight="44.0" prefWidth="571.0" GridPane.rowIndex="3">
<opaqueInsets>
<Insets />
</opaqueInsets>
<GridPane.margin>
<Insets left="50.0" />
</GridPane.margin>
<padding>
<Insets top="60.0" />
</padding></VBox>
<Text strokeType="OUTSIDE" strokeWidth="0.0" text="Finish!" wrappingWidth="623.9530334472656" GridPane.rowIndex="1">
<font>
<Font size="75.0" />
</font>
<GridPane.margin>
<Insets left="50.0" />
</GridPane.margin>
</Text>
</children>
</GridPane>
</children>
</AnchorPane>
+40 -4
View File
@@ -1,11 +1,47 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.canvas.*?>
<?import java.lang.*?> <?import java.lang.*?>
<?import javafx.scene.*?>
<?import javafx.scene.canvas.*?>
<?import javafx.scene.text.*?>
<?import javafx.scene.canvas.Canvas?>
<?import javafx.scene.control.*?>
<?import javafx.scene.Group?>
<?import javafx.scene.layout.*?> <?import javafx.scene.layout.*?>
<?import javafx.scene.text.Text?>
<AnchorPane fx:id="contentPane" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="1080.0" prefWidth="1920.0" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="seng302.controllers.Controller"> <GridPane maxHeight="745.0" maxWidth="1200.0" minHeight="745.0" minWidth="1200.0" prefHeight="745.0" prefWidth="1200.0" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="seng302.controllers.Controller">
<columnConstraints>
<ColumnConstraints hgrow="ALWAYS" minWidth="-Infinity" prefWidth="1200.0" />
</columnConstraints>
<rowConstraints>
<RowConstraints maxHeight="548.0" minHeight="10.0" prefHeight="29.0" />
<RowConstraints maxHeight="586.0" minHeight="10.0" prefHeight="582.0" valignment="CENTER" vgrow="ALWAYS" />
<RowConstraints fillHeight="false" maxHeight="286.0" minHeight="10.0" prefHeight="101.0" valignment="CENTER" vgrow="NEVER" />
</rowConstraints>
<children> <children>
<!--<fx:include source="RaceView.fxml" fx:id="raceView"/>--> <GridPane GridPane.rowIndex="1">
<columnConstraints>
<ColumnConstraints halignment="CENTER" hgrow="ALWAYS" maxWidth="1.7976931348623157E308" minWidth="10.0" prefWidth="600.0" />
<ColumnConstraints halignment="CENTER" hgrow="ALWAYS" maxWidth="500.0" minWidth="500.0" prefWidth="500.0" />
</columnConstraints>
<rowConstraints>
<RowConstraints minHeight="10.0" vgrow="ALWAYS" />
<RowConstraints />
<RowConstraints />
</rowConstraints>
<children>
<AnchorPane maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308" minHeight="-Infinity" minWidth="-Infinity" style="-fx-background-color: lightskyblue;" GridPane.halignment="RIGHT" GridPane.hgrow="SOMETIMES" GridPane.valignment="CENTER" GridPane.vgrow="ALWAYS">
<children>
<Canvas fx:id="courseCanvas" height="595.0" layoutY="-4.0" width="680.0" />
<Group fx:id="boatGroup" />
</children> </children>
</AnchorPane> </AnchorPane>
</children>
</GridPane>
<Button fx:id="playPauseButton" disable="true" mnemonicParsing="false" onAction="#playPause" text="Play" GridPane.halignment="CENTER" GridPane.rowIndex="2">
<font>
<Font size="30.0" />
</font></Button>
</children>
</GridPane>
-59
View File
@@ -1,59 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.text.*?>
<?import javafx.scene.shape.*?>
<?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?>
<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">
<columnConstraints>
<ColumnConstraints maxWidth="246.0" minWidth="246.0" prefWidth="246.0" />
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="1034.0" />
</columnConstraints>
<rowConstraints>
<RowConstraints minHeight="500.0" prefHeight="30.0" vgrow="SOMETIMES" />
<RowConstraints />
<RowConstraints />
</rowConstraints>
<children>
<AnchorPane prefHeight="200.0" prefWidth="200.0" GridPane.rowSpan="3">
<children>
<Label layoutX="11.0" layoutY="259.0" text="Team Position" />
<Label layoutX="13.0" layoutY="432.0" text="Settings" />
<Label layoutX="11.0" layoutY="14.0" text="Timer" />
<Label layoutX="11.0" layoutY="88.0" text="Wind direction" />
<Circle fx:id="windBackgroundCircle" blendMode="DARKEN" fill="#76baf8" layoutX="110.0" layoutY="166.0" radius="35.0" stroke="#686868" strokeType="INSIDE" strokeWidth="3.0" />
<Text fx:id="windArrowText" fill="#686868" layoutX="86.0" layoutY="186.0" strokeType="OUTSIDE" strokeWidth="0.0" text="↑">
<font>
<Font name="AdobeArabic-Regular" size="55.0" />
</font>
</Text>
<Text fx:id="windDirectionText" fill="#a8a7a7" layoutX="171.0" layoutY="214.0" strokeType="OUTSIDE" strokeWidth="0.0" text="0.0°" textAlignment="RIGHT">
<font>
<Font name="System Bold" size="13.0" />
</font>
</Text>
<CheckBox fx:id="toggleAnnotation" layoutX="27.0" layoutY="462.0" mnemonicParsing="false" selected="true" text="Show annotations" />
<CheckBox fx:id="toggleFps" layoutX="27.0" layoutY="488.0" mnemonicParsing="false" prefHeight="18.0" prefWidth="143.0" selected="true" text="Show FPS" />
<VBox fx:id="positionVbox" layoutX="12.0" layoutY="280.0" prefHeight="140.0" prefWidth="200.0" />
<Pane layoutX="11.0" layoutY="30.0" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="51.0" prefWidth="193.0">
<children>
<Text fx:id="timerLabel" layoutX="6.0" layoutY="37.0" strokeType="OUTSIDE" strokeWidth="0.0" text="00:00" textAlignment="CENTER" wrappingWidth="181.0">
<font>
<Font size="34.0" />
</font>
</Text>
</children>
</Pane>
</children>
</AnchorPane>
<AnchorPane fx:id="contentAnchorPane" prefHeight="960.0" prefWidth="1280.0" GridPane.columnIndex="1" GridPane.halignment="LEFT" GridPane.rowSpan="2147483647" GridPane.valignment="TOP">
<children>
<fx:include fx:id="includedCanvas" source="CanvasView.fxml" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0" />
</children></AnchorPane>
</children>
</GridPane>
-35
View File
@@ -1,35 +0,0 @@
package seng302;
import org.junit.Test;
import seng302.models.Boat;
import static org.junit.Assert.assertEquals;
/**
* Unit test for the Team class.
*/
public class BoatTest {
@Test
public void testBoatCreation() {
Boat boat1 = new Boat("Team 1");
assertEquals(boat1.getTeamName(), "Team 1");
assertEquals(boat1.getVelocity(), (double) 10.0, 1e-15);
}
@Test
public void testChangeTeamName() {
Boat boat1 = new Boat("Team 1");
boat1.setTeamName("Team 2");
assertEquals(boat1.getTeamName(), "Team 2");
}
@Test
public void testSetVelocity() {
Boat boat1 = new Boat("Team 1", 29.0, "");
assertEquals(boat1.getVelocity(), (double) 29.0, 1e-15);
boat1.setVelocity(12.0);
assertEquals(boat1.getVelocity(), (double)12.0, 1e-15);
}
}
-45
View File
@@ -1,45 +0,0 @@
package seng302;
import javafx.scene.paint.Color;
import org.junit.Test;
import seng302.models.Boat;
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;
/**
* Created by ryan_ on 16/03/2017.
*/
public class ColorsTest {
//@Test
public void testNextColor() {
List<Boat> 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<Color> enumColors = new ArrayList<>();
while (count < 6) {
Color color = Colors.getColor();
enumColors.add(color);
count++;
}
List<Color> boatColors = new ArrayList<>();
for (Boat boat : boats) {
Color color = boat.getColor();
boatColors.add(color);
}
assertEquals(enumColors, boatColors);
}
}
-38
View File
@@ -1,38 +0,0 @@
package seng302;
import org.junit.Test;
import seng302.models.Boat;
import seng302.models.Event;
import seng302.models.mark.SingleMark;
import static org.junit.Assert.assertEquals;
/**
* Test for Event class
* Created by Haoming on 7/03/17.
*/
public class EventTest {
@Test
public void getTimeString() throws Exception {
Boat boat = new Boat("testBoat");
Event event = new Event(1231242.2, boat, new SingleMark("mark1"), new SingleMark("mark2"), 0);
assertEquals("20:31:242", event.getTimeString());
}
@Test
public void testBoatHeading() throws Exception {
Boat boat = new Boat("testBoat");
Event event = new Event(1231242.2, boat, new SingleMark("mark1", 142.5, 122.1), new SingleMark("mark2", 121.9,99.2), 0);
assertEquals(event.getBoatHeading(), 228.0266137055349, 1e-15);
}
@Test
public void testDistanceBetweenMarks() throws Exception {
Boat boat = new Boat("testBoat");
Event event = new Event(1231242.2, boat, new SingleMark("mark1", 142.5, 122.1), new SingleMark("mark2", 121.9,99.2), 0);
assertEquals(event.getDistanceBetweenMarks(), 339059.653830461, 1e-15);
}
}
-54
View File
@@ -1,54 +0,0 @@
package seng302;
import org.junit.Test;
import seng302.models.Leg;
import seng302.models.mark.SingleMark;
import static org.junit.Assert.assertEquals;
/**
* Unit test for the Leg class.
*/
public class LegTest {
/**
* Test creation of the leg by specifying a string
* for the marker label
*/
@Test
public void testLegCreationUsingMarkerLabel() {
Leg leg = new Leg(010, 100, "SingleMark");
assertEquals(leg.getHeading(), 010);
assertEquals(leg.getDistance(), 100);
assertEquals(leg.getMarkerLabel(), "SingleMark");
assertEquals(leg.getIsFinishingLeg(), false);
}
/**
* Test creation of the leg by providing a
* SingleMark object
*/
@Test
public void testLegCreation() {
Leg leg = new Leg(010, 100, new SingleMark("SingleMark"));
assertEquals(leg.getHeading(), 010);
assertEquals(leg.getDistance(), 100);
assertEquals(leg.getMarkerLabel(), "SingleMark");
assertEquals(leg.getIsFinishingLeg(), false);
}
/**
* Test changing whether or not a
* leg is the finishing leg
*/
@Test
public void testSetFinishLeg() {
Leg leg = new Leg(010, 100, "SingleMark");
leg.setFinishingLeg(true);
assertEquals(leg.getIsFinishingLeg(), true);
}
}
-41
View File
@@ -1,41 +0,0 @@
package seng302;
import org.junit.Test;
import seng302.models.Boat;
import seng302.models.Race;
import java.lang.reflect.Array;
import static org.junit.Assert.assertEquals;
/**
* Unit test for the Race class.
*/
public class RaceTest {
/**
* Test that all boats were added to the race
*/
@Test
public void testAddingBoatsToRace() {
Boat boat1 = new Boat("Team 1");
Boat boat2 = new Boat("Team 2");
Race race = new Race();
race.addBoat(boat1);
race.addBoat(boat2);
assertEquals(Array.getLength(race.getBoats()), 2);
}
@Test
public void testGetShuffledBoats(){
Boat boat1 = new Boat("Team 1");
Boat boat2 = new Boat("Team 2");
Race race = new Race();
race.addBoat(boat1);
race.addBoat(boat2);
assertEquals(Array.getLength(race.getShuffledBoats()), 2);
}
}
-25
View File
@@ -1,25 +0,0 @@
package seng302;
import org.junit.Test;
import seng302.controllers.RaceViewController;
import static org.junit.Assert.assertTrue;
public class TestRaceTimer {
@Test
public void testPositiveTimeString(){
RaceViewController controller = new RaceViewController();
String result = controller.convertTimeToMinutesSeconds(61);
assertTrue(result.equals("01:01"));
}
@Test
public void testNegativeTimeString(){
RaceViewController controller = new RaceViewController();
String result = controller.convertTimeToMinutesSeconds(-61);
assertTrue(result.equals("-01:01"));
}
}
@@ -1,44 +0,0 @@
package seng302.models.mark;
import org.junit.Before;
import org.junit.Test;
import static org.junit.Assert.*;
/**
* Created by Haoming on 17/3/17.
*/
public class MarkTest {
private SingleMark singleMark1;
private SingleMark singleMark2;
private GateMark gateMark;
@Before
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());
}
@Test
public void getName() throws Exception {
assertEquals("testMark_SM1", this.singleMark1.getName());
assertEquals("testMark_GM", this.gateMark.getName());
}
@Test
public void getMarkType() throws Exception {
assertTrue(this.singleMark2.getMarkType() == MarkType.SINGLE_MARK);
assertTrue(this.gateMark.getMarkType() == MarkType.GATE_MARK);
}
@Test
public void getMarkContent() throws Exception {
assertEquals(12.23234, this.singleMark1.getLatitude(), 1e-10);
assertEquals(-34.342, this.singleMark1.getLongitude(), 1e-10);
assertEquals("testMark_SM1", this.gateMark.getSingleMark1().getName());
assertEquals(-34.352, this.gateMark.getSingleMark2().getLongitude(), 1e-10);
}
}
@@ -1,42 +0,0 @@
package seng302.models.parsers;
import org.junit.Before;
import org.junit.Test;
import static org.junit.Assert.*;
/**
* Created by Haoming on 23/03/17.
*/
public class ConfigParserTest {
private ConfigParser cp;
@Before
public void initializeParser() throws Exception {
cp = new ConfigParser("/config/config.xml");
}
@Test
public void getWindDirection() throws Exception {
assertEquals(135, cp.getWindDirection(), 1e-10);
}
@Test
public void getTimeScale() throws Exception {
assertEquals(10.0, cp.getTimeScale(), 1e-10);
}
@Test
public void getDoubleByTagName() throws Exception {
assertEquals(6, cp.getDoubleByTagName("race-size", 0), 1e-10);
assertEquals(100, cp.getDoubleByTagName("noTag", 100), 1e-10);
}
@Test
public void getStringByTagName() throws Exception {
assertEquals("AC35", cp.getStringByTagName("race-name", "11"));
assertEquals("oops", cp.getStringByTagName("noTag", "oops"));
}
}
@@ -1,60 +0,0 @@
package seng302.models.parsers;
import org.junit.Before;
import org.junit.Test;
import seng302.models.mark.*;
import java.util.ArrayList;
import static org.junit.Assert.*;
/**
* To test if course parser works as expected.
* Created by Haoming on 17/03/17.
*/
public class CourseParserTest {
private CourseParser cp;
@Before
public void initializeParser() throws Exception {
cp = new CourseParser("/config/course.xml");
}
@Test
public void getGates() throws Exception {
ArrayList<Mark> course = cp.getCourse();
assertTrue(MarkType.GATE_MARK == course.get(0).getMarkType());
GateMark gateMark1 = (GateMark) course.get(0);
assertEquals(32.293771, gateMark1.getSingleMark2().getLatitude(), 0.00000001);
assertEquals(-64.855242, gateMark1.getSingleMark2().getLongitude(), 0.00000001);
GateMark gateMark2 = (GateMark) course.get(5);
assertEquals("Finish1", gateMark2.getSingleMark1().getName());
assertEquals("Finish2", gateMark2.getSingleMark2().getName());
assertEquals(32.317257, gateMark2.getSingleMark2().getLatitude(), 0.00000001);
assertEquals(-64.83626, gateMark2.getSingleMark2().getLongitude(), 0.00000001);
}
@Test
public void getMarks() throws Exception {
ArrayList<Mark> course = cp.getCourse();
assertEquals("Mid Mark", course.get(1).getName());
}
@Test
public void getOrder() {
ArrayList<Mark> course = cp.getCourse();
assertEquals(6, course.size());
assertEquals("Start", course.get(0).getName());
assertEquals("Mid Mark", course.get(1).getName());
assertEquals("Leeward Gate", course.get(2).getName());
assertEquals("Windward Gate", course.get(3).getName());
assertEquals("Leeward Gate", course.get(4).getName());
assertEquals("Finish", course.get(5).getName());
}
}
@@ -1,35 +0,0 @@
package seng302.models.parsers;
import org.junit.Before;
import org.junit.Test;
import seng302.models.Boat;
import java.util.ArrayList;
import static org.junit.Assert.*;
/**
* Created by Haoming on 18/03/17.
*/
public class TeamsParserTest {
private TeamsParser tp;
@Before
public void readFile() {
tp = new TeamsParser("/config/teams.xml");
}
@Test
public void getBoats() throws Exception {
ArrayList<Boat> boats = tp.getBoats();
assertEquals(6, boats.size(), 1e-10);
assertEquals("Oracle Team USA", boats.get(0).getTeamName());
//assertEquals(30.9, boats.get(0).getVelocity(), 1e-10);
assertEquals("Groupama Team France", boats.get(5).getTeamName());
//assertEquals(45.6, boats.get(5).getVelocity(), 1e-10);
}
}