Merge branch 'develop' into Story64_SailsAnimations

# Conflicts:
#	src/main/java/seng302/model/Yacht.java
This commit is contained in:
Kusal Ekanayake
2017-08-13 14:34:42 +12:00
27 changed files with 1270 additions and 719 deletions
+229 -38
View File
@@ -1,7 +1,5 @@
package seng302.model;
import static seng302.utilities.GeoUtility.getGeoCoordinate;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
@@ -12,8 +10,12 @@ import javafx.beans.property.ReadOnlyDoubleWrapper;
import javafx.beans.property.ReadOnlyLongProperty;
import javafx.beans.property.ReadOnlyLongWrapper;
import javafx.scene.paint.Color;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import seng302.gameServer.GameState;
import seng302.model.mark.CompoundMark;
import seng302.model.mark.Mark;
import seng302.utilities.GeoUtility;
/**
* Yacht class for the racing boat.
@@ -29,6 +31,11 @@ public class Yacht {
void notifyLocation(Yacht yacht, double lat, double lon, double heading, double velocity, boolean sailIn);
}
private Logger logger = LoggerFactory.getLogger(Yacht.class);
private static final Double ROUNDING_DISTANCE = 50d; // TODO: 3/08/17 wmu16 - Look into this value further
//BOTH AFAIK
private String boatType;
private Integer sourceId;
@@ -38,12 +45,11 @@ public class Yacht {
private String country;
private Long estimateTimeAtFinish;
private Long timeTillNext;
private Integer currentMarkSeqID = 0;
private Long markRoundTime;
private CompoundMark nextMark;
private Double distanceToCurrentMark;
private Long timeTillNext;
private Double heading;
private Double lat;
private Double lon;
private Integer legNumber = 0;
//SERVER SIDE
@@ -54,6 +60,13 @@ public class Yacht {
private Integer boatStatus;
private Double velocity;
//MARK ROUNDING INFO
private GeoPoint lastLocation; //For purposes of mark rounding calculations
private Boolean hasEnteredRoundingZone; //The distance that the boat must be from the mark to round
private Boolean hasPassedLine;
private Boolean hasPassedThroughGate;
private Boolean finishedRace;
//CLIENT SIDE
private List<YachtLocationListener> locationListeners = new ArrayList<>();
private ReadOnlyDoubleWrapper velocityProperty = new ReadOnlyDoubleWrapper();
@@ -73,8 +86,14 @@ public class Yacht {
this.boatName = boatName;
this.country = country;
this.location = new GeoPoint(57.670341, 11.826856);
this.lastLocation = location;
this.heading = 120.0; //In degrees
this.velocity = 0d; //in mms-1
this.hasEnteredRoundingZone = false;
this.hasPassedLine = false;
this.hasPassedThroughGate = false;
this.finishedRace = false;
}
/**
@@ -110,10 +129,178 @@ public class Yacht {
}
}
Double metersCovered = velocity * secondsElapsed;
location = getGeoCoordinate(location, heading, metersCovered);
//UPDATE BOAT LOCATION
lastLocation = location;
location = GeoUtility.getGeoCoordinate(location, heading, velocity * secondsElapsed);
//CHECK FOR MARK ROUNDING
if (!finishedRace) {
checkForLegProgression();
}
// TODO: 3/08/17 wmu16 - Implement line cross check here
}
/**
* Calculates the distance to the next mark (closest of the two if a gate mark). For purposes
* of mark rounding
* @return A distance in metres. Returns -1 if there is no next mark
* @throws IndexOutOfBoundsException If the next mark is null (ie the last mark in the race)
* Check first using {@link seng302.model.mark.MarkOrder#isLastMark(Integer)}
*/
public Double calcDistanceToCurrentMark() throws IndexOutOfBoundsException {
CompoundMark nextMark = GameState.getMarkOrder().getCurrentMark(currentMarkSeqID);
if (nextMark.isGate()) {
Mark sub1 = nextMark.getSubMark(1);
Mark sub2 = nextMark.getSubMark(2);
Double distance1 = GeoUtility.getDistance(location, sub1);
Double distance2 = GeoUtility.getDistance(location, sub2);
return (distance1 < distance2) ? distance1 : distance2;
} else {
return GeoUtility.getDistance(location, nextMark.getSubMark(1));
}
}
/**
* 4 Different cases of progression in the race
* 1 - Passing the start line
* 2 - Passing any in-race Gate
* 3 - Passing any in-race Mark
* 4 - Passing the finish line
*/
private void checkForLegProgression() {
CompoundMark currentMark = GameState.getMarkOrder().getCurrentMark(currentMarkSeqID);
if (currentMarkSeqID == 0) {
checkStartLineCrossing(currentMark);
} else if (GameState.getMarkOrder().isLastMark(currentMarkSeqID)) {
checkFinishLineCrossing(currentMark);
} else if (currentMark.isGate()) {
checkGateRounding(currentMark);
} else {
checkMarkRounding(currentMark);
}
}
/**
* If we pass the start line gate in the correct direction, progress
*
* @param currentMark The current gate
*/
private void checkStartLineCrossing(CompoundMark currentMark) {
Mark mark1 = currentMark.getSubMark(1);
Mark mark2 = currentMark.getSubMark(2);
CompoundMark nextMark = GameState.getMarkOrder().getNextMark(currentMarkSeqID);
Integer crossedLine = GeoUtility.checkCrossedLine(mark1, mark2, lastLocation, location);
if (crossedLine > 0) {
Boolean isClockwiseCross = GeoUtility.isClockwise(mark1, mark2, nextMark.getMidPoint());
if (crossedLine == 2 && isClockwiseCross || crossedLine == 1 && !isClockwiseCross) {
currentMarkSeqID++;
logMarkRounding(currentMark);
}
}
}
/**
* This algorithm checks for mark rounding. And increments the currentMarSeqID number attribute
* of the yacht if so.
* A visual representation of this algorithm can be seen on the Wiki under
* 'mark passing algorithm'
*/
private void checkMarkRounding(CompoundMark currentMark) {
distanceToCurrentMark = calcDistanceToCurrentMark();
GeoPoint nextPoint = GameState.getMarkOrder().getNextMark(currentMarkSeqID).getMidPoint();
GeoPoint prevPoint = GameState.getMarkOrder().getPreviousMark(currentMarkSeqID)
.getMidPoint();
GeoPoint midPoint = GeoUtility.getDirtyMidPoint(nextPoint, prevPoint);
//1 TEST FOR ENTERING THE ROUNDING DISTANCE
if (distanceToCurrentMark < ROUNDING_DISTANCE) {
hasEnteredRoundingZone = true;
}
//In case current mark is a gate, loop through all marks just in case
for (Mark thisCurrentMark : currentMark.getMarks()) {
if (GeoUtility.isPointInTriangle(lastLocation, location, midPoint, thisCurrentMark)) {
hasPassedLine = true;
}
}
if (hasPassedLine && hasEnteredRoundingZone) {
currentMarkSeqID++;
hasPassedLine = false;
hasEnteredRoundingZone = false;
hasPassedThroughGate = false;
logMarkRounding(currentMark);
}
}
/**
* Checks if a gate line has been crossed and in the correct direction
*
* @param currentMark The current gate
*/
private void checkGateRounding(CompoundMark currentMark) {
Mark mark1 = currentMark.getSubMark(1);
Mark mark2 = currentMark.getSubMark(2);
CompoundMark prevMark = GameState.getMarkOrder().getPreviousMark(currentMarkSeqID);
CompoundMark nextMark = GameState.getMarkOrder().getNextMark(currentMarkSeqID);
Integer crossedLine = GeoUtility.checkCrossedLine(mark1, mark2, lastLocation, location);
//We have crossed the line
if (crossedLine > 0) {
Boolean isClockwiseCross = GeoUtility.isClockwise(mark1, mark2, prevMark.getMidPoint());
//Check we cross the line in the correct direction
if (crossedLine == 1 && isClockwiseCross || crossedLine == 2 && !isClockwiseCross) {
hasPassedThroughGate = true;
}
}
Boolean prevMarkSide = GeoUtility.isClockwise(mark1, mark2, prevMark.getMidPoint());
Boolean nextMarkSide = GeoUtility.isClockwise(mark1, mark2, nextMark.getMidPoint());
if (hasPassedThroughGate) {
//Check if we need to round this gate after passing through
if (prevMarkSide == nextMarkSide) {
checkMarkRounding(currentMark);
} else {
currentMarkSeqID++;
logMarkRounding(currentMark);
}
}
}
/**
* If we pass the finish gate in the correct direction
*
* @param currentMark The current gate
*/
private void checkFinishLineCrossing(CompoundMark currentMark) {
Mark mark1 = currentMark.getSubMark(1);
Mark mark2 = currentMark.getSubMark(2);
CompoundMark prevMark = GameState.getMarkOrder().getPreviousMark(currentMarkSeqID);
Integer crossedLine = GeoUtility.checkCrossedLine(mark1, mark2, lastLocation, location);
if (crossedLine > 0) {
Boolean isClockwiseCross = GeoUtility.isClockwise(mark1, mark2, prevMark.getMidPoint());
if (crossedLine == 1 && isClockwiseCross || crossedLine == 2 && !isClockwiseCross) {
currentMarkSeqID++;
finishedRace = true;
logMarkRounding(currentMark);
logger.debug(sourceId + " finished");
// TODO: 8/08/17 wmu16 - Do something!
}
}
}
public void adjustHeading(Double amount) {
Double newVal = heading + amount;
lastHeading = heading;
@@ -192,7 +379,6 @@ public class Yacht {
}
private void turnTowardsHeading(Double newHeading) {
System.out.println(newHeading);
if (heading < 90 && newHeading > 270) {
adjustHeading(-TURN_STEP);
} else {
@@ -282,7 +468,6 @@ public class Yacht {
this.velocityProperty.set(velocity);
}
public void setMarkRoundingTime(Long markRoundingTime) {
this.markRoundTime = markRoundingTime;
}
@@ -319,28 +504,21 @@ public class Yacht {
this.lastMarkRounded = lastMarkRounded;
}
public void setNextMark(CompoundMark nextMark) {
this.nextMark = nextMark;
public GeoPoint getLocation() {
return location;
}
public CompoundMark getNextMark(){
return nextMark;
}
public Double getLat() {
return lat;
}
public void setLat(Double lat) {
this.lat = lat;
}
public Double getLon() {
return lon;
}
public void setLon(Double lon) {
this.lon = lon;
/**
* Sets the current location of the boat in lat and long whilst preserving the last location
*
* @param lat Latitude
* @param lng Longitude
*/
public void setLocation(Double lat, Double lng) {
lastLocation.setLat(location.getLat());
lastLocation.setLng(location.getLng());
location.setLat(lat);
location.setLng(lng);
}
public Double getHeading() {
@@ -360,10 +538,6 @@ public class Yacht {
return boatName;
}
public GeoPoint getLocation() {
return location;
}
public void updateTimeSinceLastMarkProperty(long timeSinceLastMark) {
this.timeSinceLastMarkProperty.set(timeSinceLastMark);
}
@@ -397,21 +571,38 @@ public class Yacht {
this.velocity = velocity;
}
public Double getDistanceToCurrentMark() {
return distanceToCurrentMark;
}
public Boolean getClientSailsIn(){
return clientSailsIn;
}
public void updateLocation (double lat, double lon, double heading, double velocity) {
this.lat = lat;
this.lon = lon;
public void updateLocation (double lat, double lng, double heading, double velocity) {
setLocation(lat, lng);
this.heading = heading;
this.velocity = velocity;
updateVelocityProperty(velocity);
for (YachtLocationListener yll : locationListeners) {
yll.notifyLocation(this, lat, lon, heading, velocity, clientSailsIn);
yll.notifyLocation(this, lat, lng, heading, velocity);
}
}
private void logMarkRounding(CompoundMark currentMark) {
String typeString = "mark";
if (currentMark.isGate()) {
typeString = "gate";
}
logger.debug(
String.format("BoatID %d passed %s %s with id %d. Now on leg %d",
sourceId,
typeString,
currentMark.getMarks().get(0).getName(),
currentMark.getId(),
currentMarkSeqID));
}
public void addLocationListener (YachtLocationListener listener) {
locationListeners.add(listener);
}
@@ -1,8 +1,9 @@
package seng302.model.mark;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import seng302.model.GeoPoint;
import seng302.utilities.GeoUtility;
public class CompoundMark {
@@ -10,18 +11,17 @@ public class CompoundMark {
private String name;
private List<Mark> marks = new ArrayList<>();
private GeoPoint midPoint;
public CompoundMark(int markID, String name) {
this.compoundMarkId = markID;
public CompoundMark(int markID, String name, List<Mark> marks) {
this.compoundMarkId = markID;
this.name = name;
}
public void addSubMarks(Mark... marks) {
this.marks.addAll(Arrays.asList(marks));
}
public void addSubMarks(List<Mark> marks) {
this.marks.addAll(marks);
this.marks.addAll(marks);
if (marks.size() > 1) {
this.midPoint = GeoUtility.getDirtyMidPoint(marks.get(0), marks.get(1));
} else {
this.midPoint = marks.get(0);
}
}
/**
@@ -68,6 +68,16 @@ public class CompoundMark {
}
}
/**
* NOTE: This is a 'dirty' mid point as it is simply calculated as an xy point would be.
* NO CHECKING FOR LAT / LNG WRAPPING IS DONE IN CREATION OF THIS MIDPOINT
*
* @return GeoPoint of the midpoint of the two marks, or the one mark if there is only one
*/
public GeoPoint getMidPoint() {
return midPoint;
}
/**
* Returns whether or not this CompoundMark is a Gate. It is generally cleaner to program to a
* specific singleMark or the list of marks.
@@ -86,4 +96,14 @@ public class CompoundMark {
public List<Mark> getMarks () {
return marks;
}
@Override
public int hashCode() {
int hash = 0;
for (Mark mark : marks) {
hash += Double.hashCode(mark.getSourceID()) + Double.hashCode(mark.getLat())
+ Double.hashCode(mark.getLng()) + mark.getName().hashCode();
}
return hash + getName().hashCode() + Integer.hashCode(getId());
}
}
@@ -0,0 +1,133 @@
package seng302.model.mark;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import seng302.model.stream.xml.generator.Race;
import seng302.model.stream.xml.parser.RaceXMLData;
import seng302.utilities.XMLGenerator;
import seng302.utilities.XMLParser;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import java.io.IOException;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
/**
* Class to hold the order of the marks in the race.
*/
public class MarkOrder {
private List<CompoundMark> raceMarkOrder;
private Logger logger = LoggerFactory.getLogger(MarkOrder.class);
public MarkOrder(){
loadRaceProperties();
}
/**
* @return An ordered list of marks in the race
* OR null if the mark order could not be loaded
*/
public List<CompoundMark> getMarkOrder(){
if (raceMarkOrder == null){
logger.warn("Race order accessed but not instantiated");
return null;
}
return Collections.unmodifiableList(raceMarkOrder);
}
/**
* @param seqID The seqID of the current mark the boat is heading to
* @return A Boolean indicating if this coming mark is the last one (finish line)
*/
public Boolean isLastMark(Integer seqID) {
return seqID == raceMarkOrder.size() - 1;
}
/**
* @param currentSeqID The seqID of the current mark the boat is heading to
* @return The mark last passed
* @throws IndexOutOfBoundsException if there is no next mark.
* Check seqID != 0 first
*/
public CompoundMark getPreviousMark(Integer currentSeqID) throws IndexOutOfBoundsException{
return raceMarkOrder.get(currentSeqID - 1);
}
public CompoundMark getCurrentMark(Integer currentSeqID) {
return raceMarkOrder.get(currentSeqID);
}
/**
* @param currentSeqID The seqID of the current mark the boat is heading to
* @return The mark following the mark that the boat is heading to
* @throws IndexOutOfBoundsException if there is no next mark.
* Check using {@link #isLastMark(Integer)}
*/
public CompoundMark getNextMark(Integer currentSeqID) throws IndexOutOfBoundsException{
return raceMarkOrder.get(currentSeqID + 1);
}
/**
* Loads the race order from an XML string
* @param xml An AC35 RaceXML
* @return An ordered list of marks in the race
*/
private List<CompoundMark> loadRaceOrderFromXML(String xml){
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
DocumentBuilder db;
Document doc;
try {
db = dbf.newDocumentBuilder();
doc = db.parse(new InputSource(new StringReader(xml)));
} catch (ParserConfigurationException | IOException | SAXException e) {
logger.error("Failed to read generated race XML");
return null;
}
RaceXMLData data = XMLParser.parseRace(doc);
if (data != null){
logger.debug("Loaded RaceXML for mark order");
List<Corner> corners = data.getMarkSequence();
Map<Integer, CompoundMark> marks = data.getCompoundMarks();
List<CompoundMark> course = new ArrayList<>();
for (Corner corner : corners){
CompoundMark compoundMark = marks.get(corner.getCompoundMarkID());
course.add(compoundMark);
}
return course;
}
return null;
}
/**
* Load the raceXML and mark order
*/
private void loadRaceProperties(){
XMLGenerator generator = new XMLGenerator();
generator.setRace(new Race());
String raceXML = generator.getRaceAsXml();
if (raceXML == null){
logger.error("Failed to generate raceXML (for race properties)");
return;
}
raceMarkOrder = loadRaceOrderFromXML(raceXML);
}
}
@@ -19,7 +19,9 @@ public enum PacketType {
COURSE_WIND,
AVG_WIND,
BOAT_ACTION,
OTHER;
OTHER,
RACE_REGISTRATION_REQUEST,
RACE_REGISTRATION_RESPONSE;
public static PacketType assignPacketType(int packetType, byte[] payload){
switch(packetType){
@@ -56,6 +58,10 @@ public enum PacketType {
return AVG_WIND;
case 100:
return BOAT_ACTION;
case 101:
return RACE_REGISTRATION_REQUEST;
case 102:
return RACE_REGISTRATION_RESPONSE;
default:
}
return OTHER;