Merge branch 'master' into story1301-ingamelobby
# Conflicts: # racevisionGame/src/main/java/visualiser/Controllers/InGameLobbyController.java # racevisionGame/src/main/resources/visualiser/scenes/gameLobby.fxml
This commit is contained in:
@@ -40,53 +40,44 @@ public class Event {
|
||||
private String raceXML;
|
||||
private String regattaXML;
|
||||
private String boatXML;
|
||||
|
||||
private XMLFileType xmlFileType;
|
||||
|
||||
private Polars boatPolars;
|
||||
|
||||
/**
|
||||
* Data sources containing data from the xml files.
|
||||
*/
|
||||
RaceDataSource raceDataSource;
|
||||
BoatDataSource boatDataSource;
|
||||
RegattaDataSource regattaDataSource;
|
||||
|
||||
|
||||
private RaceDataSource raceDataSource;
|
||||
private BoatDataSource boatDataSource;
|
||||
private RegattaDataSource regattaDataSource;
|
||||
private ConnectionAcceptor connectionAcceptor;
|
||||
private LatestMessages latestMessages;
|
||||
|
||||
private CompositeCommand compositeCommand;
|
||||
|
||||
/**
|
||||
* This is used to allocate source IDs.
|
||||
*/
|
||||
private SourceIdAllocator sourceIdAllocator;
|
||||
|
||||
private RaceLogic raceLogic;
|
||||
|
||||
private Thread raceThread;
|
||||
|
||||
private Thread connectionThread;
|
||||
|
||||
private int mapIndex;
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Constructs an event, using various XML files.
|
||||
* @param singlePlayer Whether or not to create a single player event.
|
||||
* @param mapIndex Specifies which map to use.
|
||||
* @param raceLength The length of the race, in milliseconds.
|
||||
* @throws EventConstructionException Thrown if we cannot create an Event for any reason.
|
||||
*/
|
||||
public Event(boolean singlePlayer, int mapIndex) throws EventConstructionException {
|
||||
public Event(boolean singlePlayer, int mapIndex, int raceLength) throws
|
||||
EventConstructionException {
|
||||
PolarParser.parseNewPolars("mock/polars/acc_polars.csv");
|
||||
this.mapIndex = mapIndex;
|
||||
String raceXMLFile;
|
||||
String boatsXMLFile = "mock/mockXML/boatTest.xml";
|
||||
String regattaXMLFile = "mock/mockXML/regattaTest.xml";
|
||||
switch (mapIndex){
|
||||
case 0:raceXMLFile = "mock/mockXML/raceSixPlayers.xml";
|
||||
case 0:raceXMLFile = "mock/mockXML/ac35MapLayout.xml";
|
||||
break;
|
||||
case 1:raceXMLFile = "mock/mockXML/oMapLayout.xml";
|
||||
break;
|
||||
@@ -99,13 +90,12 @@ public class Event {
|
||||
boatsXMLFile = "mock/mockXML/boatTutorial.xml";
|
||||
regattaXMLFile = "mock/mockXML/regattaTutorial.xml";
|
||||
break;
|
||||
default: raceXMLFile = "mock/mockXML/raceSixPlayers.xml";
|
||||
default: raceXMLFile = "mock/mockXML/ac35MapLayout.xml";
|
||||
|
||||
}
|
||||
|
||||
|
||||
if (singlePlayer) {
|
||||
raceXMLFile = "mock/mockXML/raceSinglePlayer.xml";
|
||||
boatsXMLFile = "mock/mockXML/boatsSinglePlayer.xml";
|
||||
}
|
||||
|
||||
@@ -114,14 +104,17 @@ public class Event {
|
||||
|
||||
//Read XML files.
|
||||
try {
|
||||
//this.raceXML = RaceXMLCreator.alterRaceToWind(raceXMLFile, 90);
|
||||
this.raceXML = Event.setRaceXMLAtCurrentTimeToNow(XMLReader.readXMLFileToString(raceXMLFile, StandardCharsets.UTF_8));
|
||||
|
||||
if(mapIndex==4){
|
||||
//this.raceXML = Event.setRaceXMLAtCurrentTimeToNow(XMLReader.readXMLFileToString(raceXMLFile, StandardCharsets.UTF_8), 1000, 5000);
|
||||
this.raceXML = RaceXMLCreator.alterRaceToWind(this.raceXML, XMLFileType.Contents, -1, true);
|
||||
//This is the tutorial map.
|
||||
this.raceXML = Event.setRaceXMLAtCurrentTimeToNow(XMLReader.readXMLFileToString(raceXMLFile, StandardCharsets.UTF_8), 1000, 5000);
|
||||
this.raceXML = RaceXMLCreator.alterRaceToWind(this.raceXML, XMLFileType.Contents, -1);
|
||||
|
||||
} else {
|
||||
this.raceXML = RaceXMLCreator.alterRaceToWind(this.raceXML, XMLFileType.Contents, windAngle, false);
|
||||
this.raceXML = RaceXMLCreator.scaleRaceSize(raceXML, windSpeed, 15 * 60 * 1000);
|
||||
this.raceXML = Event.setRaceXMLAtCurrentTimeToNow(XMLReader.readXMLFileToString(raceXMLFile, StandardCharsets.UTF_8));
|
||||
this.raceXML = RaceXMLCreator.alterRaceToWind(this.raceXML, XMLFileType.Contents, windAngle);
|
||||
this.raceXML = RaceXMLCreator.scaleRaceSize(raceXML,
|
||||
windSpeed, raceLength);
|
||||
}
|
||||
|
||||
this.boatXML = XMLReader.readXMLFileToString(boatsXMLFile, StandardCharsets.UTF_8);
|
||||
@@ -171,20 +164,20 @@ public class Event {
|
||||
}
|
||||
|
||||
|
||||
RaceLogic newRace = new RaceLogic(
|
||||
this.raceLogic = new RaceLogic(
|
||||
mockRace,
|
||||
this.latestMessages,
|
||||
this.compositeCommand);
|
||||
|
||||
this.raceThread = new Thread(newRace, "Event.Start()->RaceLogic thread");
|
||||
this.raceThread = new Thread(raceLogic, "Event.Start()->RaceLogic thread");
|
||||
raceThread.start();
|
||||
|
||||
|
||||
//Create connection acceptor.
|
||||
this.sourceIdAllocator = new SourceIdAllocator(newRace.getRace());
|
||||
this.sourceIdAllocator = new SourceIdAllocator(raceLogic.getRace());
|
||||
|
||||
try {
|
||||
this.connectionAcceptor = new ConnectionAcceptor(latestMessages, compositeCommand, sourceIdAllocator, newRace);
|
||||
this.connectionAcceptor = new ConnectionAcceptor(latestMessages, compositeCommand, sourceIdAllocator, raceLogic);
|
||||
|
||||
} catch (IOException e) {
|
||||
throw new EventConstructionException("Could not create ConnectionAcceptor.", e);
|
||||
@@ -217,16 +210,13 @@ public class Event {
|
||||
}
|
||||
|
||||
public static String setRaceXMLAtCurrentTimeToNow(String raceXML, long racePreStartTime, long racePreparatoryTime){
|
||||
//The start time is current time + 4 minutes. prestart is 3 minutes, and we add another minute.
|
||||
|
||||
long millisecondsToAdd = racePreStartTime + racePreparatoryTime;
|
||||
|
||||
long secondsToAdd = millisecondsToAdd / 1000;
|
||||
long minutesToAdd = 10;
|
||||
|
||||
DateTimeFormatter dateFormat = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ssZ");
|
||||
ZonedDateTime creationTime = ZonedDateTime.now();
|
||||
raceXML = raceXML.replace("RACE_CREATION_TIME", dateFormat.format(creationTime));
|
||||
raceXML = raceXML.replace("RACE_START_TIME", dateFormat.format(creationTime.plusSeconds(secondsToAdd)));
|
||||
raceXML = raceXML.replace("RACE_START_TIME", dateFormat.format(creationTime.plusMinutes(minutesToAdd)));
|
||||
|
||||
return raceXML;
|
||||
}
|
||||
@@ -241,4 +231,9 @@ public class Event {
|
||||
return new HostGame(ip, 3779,(byte) 1,(byte) 1, RaceStatusEnum.PRESTART, (byte) 1, (byte) 1);
|
||||
}
|
||||
|
||||
|
||||
public RaceLogic getRaceLogic() {
|
||||
return raceLogic;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ public class MockBoat extends Boat {
|
||||
* 1: passed only first check
|
||||
* 2: passed first and second check
|
||||
*/
|
||||
private Integer roundingStatus = 0;
|
||||
private int roundingStatus = 0;
|
||||
|
||||
/**
|
||||
* Stores whether the boat is on autoVMG or not
|
||||
@@ -284,8 +284,8 @@ public class MockBoat extends Boat {
|
||||
(this.isStarboardSide(mark1) && this.isPortSide(mark2));
|
||||
}
|
||||
|
||||
public Integer getRoundingStatus() {
|
||||
return Integer.valueOf(roundingStatus);
|
||||
public int getRoundingStatus() {
|
||||
return roundingStatus;
|
||||
}
|
||||
|
||||
public void increaseRoundingStatus() {
|
||||
|
||||
@@ -61,12 +61,17 @@ public class MockRace extends RaceState {
|
||||
*/
|
||||
private Polars polars;
|
||||
|
||||
private ActiveObserverCommand activeObserverCommand;
|
||||
private Map<Integer, ActiveObserverCommand> activeObserverCommands;
|
||||
|
||||
private long racePreStartTime = Constants.RacePreStartTime;
|
||||
|
||||
private long racePreparatoryTime = Constants.RacePreparatoryTime;
|
||||
|
||||
/**
|
||||
* True if the race has been manually started, false otherwise.
|
||||
*/
|
||||
private boolean hasBeenStarted = false;
|
||||
|
||||
/**
|
||||
* Constructs a race object with a given RaceDataSource, BoatDataSource, and RegattaDataSource and sends events to the given mockOutput.
|
||||
* @param boatDataSource Data source for boat related data (yachts and marker boats).
|
||||
@@ -82,7 +87,7 @@ public class MockRace extends RaceState {
|
||||
this.setRaceDataSource(raceDataSource);
|
||||
this.setRegattaDataSource(regattaDataSource);
|
||||
|
||||
this.activeObserverCommand = new ActiveObserverCommand();
|
||||
this.activeObserverCommands = new HashMap<>();
|
||||
this.polars = polars;
|
||||
this.scaleFactor = timeScale;
|
||||
|
||||
@@ -104,8 +109,6 @@ public class MockRace extends RaceState {
|
||||
colliderRegistry.addCollider(mark.getMark1());
|
||||
if(mark.getMark2() != null) colliderRegistry.addCollider(mark.getMark2());
|
||||
}
|
||||
|
||||
this.colliderRegistry.addAllColliders(boats);
|
||||
}
|
||||
|
||||
|
||||
@@ -127,9 +130,10 @@ public class MockRace extends RaceState {
|
||||
getRaceDataSource().getParticipants().add(sourceID);
|
||||
|
||||
this.boats.add(mockBoat);
|
||||
this.activeObserverCommands.put(boat.getSourceID(), new ActiveObserverCommand());
|
||||
this.colliderRegistry.addCollider(mockBoat);
|
||||
|
||||
getRaceDataSource().incrementSequenceNumber();
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -157,6 +161,23 @@ public class MockRace extends RaceState {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Delays the start of the race, if needed, to ensure that the race doesn't start until host wants it to.
|
||||
* If the time until start is less that 5 minutes, it is extended to 10 minutes.
|
||||
*/
|
||||
public void delayRaceStart() {
|
||||
long timeToStart = getRaceDataSource().getStartDateTime().toInstant().toEpochMilli() - System.currentTimeMillis();
|
||||
|
||||
long fiveMinutesMilli = 5 * 60 * 1000;
|
||||
long tenMinutesMilli = 10 * 60 * 1000;
|
||||
|
||||
if ((timeToStart < fiveMinutesMilli) && (timeToStart > 0) && !hasBeenStarted) {
|
||||
startRace(tenMinutesMilli, false);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Updates the race status enumeration based on the current time.
|
||||
*/
|
||||
@@ -165,7 +186,6 @@ public class MockRace extends RaceState {
|
||||
//The millisecond duration of the race. Negative means it hasn't started, so we flip sign.
|
||||
long timeToStart = - this.getRaceClock().getDurationMilli();
|
||||
|
||||
|
||||
if (timeToStart > racePreStartTime) {
|
||||
//Time > 3 minutes is the prestart period.
|
||||
this.setRaceStatusEnum(RaceStatusEnum.PRESTART);
|
||||
@@ -195,6 +215,26 @@ public class MockRace extends RaceState {
|
||||
this.racePreparatoryTime = racePreparatoryTime;
|
||||
}
|
||||
|
||||
public long getRacePreparatoryTime() {
|
||||
return racePreparatoryTime;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Starts the race in #timeToStartMilli milliseconds.
|
||||
* @param timeToStartMilli Millseconds before starting the race.
|
||||
* @param manualStart True if the race has been manually started, false otherwise.
|
||||
*/
|
||||
public void startRace(long timeToStartMilli, boolean manualStart) {
|
||||
this.hasBeenStarted = manualStart;
|
||||
ZonedDateTime startTime = ZonedDateTime.now().plus(timeToStartMilli, ChronoUnit.MILLIS);
|
||||
|
||||
getRaceDataSource().setStartDateTime(startTime);
|
||||
getRaceDataSource().incrementSequenceNumber();
|
||||
|
||||
this.getRaceClock().setStartingTime(getRaceDataSource().getStartDateTime());
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the status of all boats in the race to RACING.
|
||||
*/
|
||||
@@ -377,6 +417,11 @@ public class MockRace extends RaceState {
|
||||
boat.setTimeSinceTackChange(boat.getTimeSinceTackChange() + updatePeriodMilliseconds);
|
||||
}
|
||||
|
||||
// Remove one unit of health for every frame spent outside boundary
|
||||
if(!finish && !GPSCoordinate.isInsideBoundary(boat.getPosition(), getBoundary())) {
|
||||
boat.updateHealth(-0.1);
|
||||
}
|
||||
|
||||
this.updateEstimatedTime(boat);
|
||||
|
||||
}
|
||||
@@ -388,7 +433,7 @@ public class MockRace extends RaceState {
|
||||
boat.getBearing()
|
||||
), boat.getBearing()) ;
|
||||
if (vmg.getSpeed() > 0) {
|
||||
boat.setCurrentSpeed(vmg.getSpeed());
|
||||
boat.setCurrentSpeed(vmg.getSpeed() * Math.pow(boat.getHealth() / 100, 0.3));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -635,7 +680,7 @@ public class MockRace extends RaceState {
|
||||
for (MockBoat boat : this.boats) {
|
||||
|
||||
//If the boat is currently racing, count it.
|
||||
if (boat.getStatus() == BoatStatusEnum.RACING) {
|
||||
if (boat.getStatus() == BoatStatusEnum.RACING && boat.getHealth()>=1) {
|
||||
numberOfActiveBoats++;
|
||||
}
|
||||
|
||||
@@ -711,11 +756,11 @@ public class MockRace extends RaceState {
|
||||
super.setChanged();
|
||||
}
|
||||
|
||||
public void addVelocityCommand(ObserverCommand c) {
|
||||
this.activeObserverCommand.changeVelocityCommand(this, c);
|
||||
public void addVelocityCommand(ObserverCommand c, int boatId) {
|
||||
this.activeObserverCommands.get(boatId).changeVelocityCommand(this, c);
|
||||
}
|
||||
|
||||
public void addAngularCommand(ObserverCommand c) {
|
||||
this.activeObserverCommand.changeAngularCommand(this, c);
|
||||
public void addAngularCommand(ObserverCommand c, int boatId) {
|
||||
this.activeObserverCommands.get(boatId).changeAngularCommand(this, c);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,6 +81,8 @@ public class RaceLogic implements RunnableWithFramePeriod, Observer {
|
||||
//Update race time.
|
||||
race.updateRaceTime(currentTime);
|
||||
|
||||
race.delayRaceStart();
|
||||
|
||||
//Update the race status based on the current time.
|
||||
race.updateRaceStatusEnum();
|
||||
|
||||
@@ -161,7 +163,7 @@ public class RaceLogic implements RunnableWithFramePeriod, Observer {
|
||||
|
||||
//As long as there is at least one boat racing, we still simulate the race.
|
||||
if (race.getNumberOfActiveBoats() != 0) {
|
||||
|
||||
//System.out.println(race.getNumberOfActiveBoats());
|
||||
//Get the time period of this frame.
|
||||
long framePeriod = currentTime - previousFrameTime;
|
||||
|
||||
@@ -173,12 +175,9 @@ public class RaceLogic implements RunnableWithFramePeriod, Observer {
|
||||
race.updatePosition(boat, framePeriod, race.getRaceClock().getDurationMilli());
|
||||
|
||||
if(race.getColliderRegistry().rayCast(boat)){
|
||||
//System.out.println("Collision!");
|
||||
//Add boat to list
|
||||
collisionBoats.add(boat);
|
||||
}
|
||||
|
||||
//System.out.println(race.getColliderRegistry().rayCast(boat));
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -64,6 +64,9 @@ public class RaceServer {
|
||||
//Parse the boat locations.
|
||||
snapshotMessages.addAll(parseBoatLocations());
|
||||
|
||||
//Parse the boat states
|
||||
snapshotMessages.addAll(parseBoatStates());
|
||||
|
||||
//Parse the marks.
|
||||
snapshotMessages.addAll(parseMarks());
|
||||
|
||||
@@ -243,7 +246,29 @@ public class RaceServer {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates BoatState messages for every boat in the race
|
||||
* @return list of BoatState messages
|
||||
*/
|
||||
private List<BoatState> parseBoatStates() {
|
||||
List<BoatState> boatStates = new ArrayList<>();
|
||||
for(MockBoat boat: race.getBoats()) {
|
||||
boatStates.add(parseIndividualBoatState(boat));
|
||||
}
|
||||
return boatStates;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a BoatState message for the current state of the given boat
|
||||
* @param boat to generate message for
|
||||
* @return BoatState message
|
||||
*/
|
||||
private BoatState parseIndividualBoatState(MockBoat boat) {
|
||||
return new BoatState(
|
||||
boat.getSourceID(),
|
||||
(int)boat.getHealth()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the race status, and returns it.
|
||||
|
||||
@@ -36,6 +36,7 @@ public class CollisionCommand extends ObserverCommand {
|
||||
@Override
|
||||
public void update(Observable o, Object arg) {
|
||||
if(GPSCoordinate.calculateDistanceMeters(boat.getPosition(), startingPosition) < distance) {
|
||||
boat.setVelocityDefault(false);
|
||||
boat.setPosition(GPSCoordinate.calculateNewPosition(boat.getPosition(), 3, azimuth));
|
||||
} else {
|
||||
race.deleteObserver(this);
|
||||
|
||||
@@ -13,7 +13,7 @@ public class SailsCommand extends ObserverCommand {
|
||||
|
||||
public SailsCommand(MockRace race, MockBoat boat, boolean sailsOut) {
|
||||
super(race, boat);
|
||||
race.addVelocityCommand(this);
|
||||
race.addVelocityCommand(this, boat.getSourceID());
|
||||
this.sailsOut = sailsOut;
|
||||
}
|
||||
|
||||
@@ -37,15 +37,18 @@ public class SailsCommand extends ObserverCommand {
|
||||
public void update(Observable o, Object arg) {
|
||||
double acceleration = 0.5;
|
||||
|
||||
if(sailsOut && boat.getCurrentSpeed() < goalVelocity) {
|
||||
boat.setCurrentSpeed(Math.min(goalVelocity, boat.getCurrentSpeed() + acceleration));
|
||||
} else if (!sailsOut && boat.getCurrentSpeed() > goalVelocity) {
|
||||
// Apply deceleration to strictly 0 speed
|
||||
boat.setCurrentSpeed(Math.max(0, boat.getCurrentSpeed() - acceleration));
|
||||
} else {
|
||||
// Release boat from SailsCommand control
|
||||
if(sailsOut) boat.setVelocityDefault(true);
|
||||
race.deleteObserver(this);
|
||||
if (!boat.isColliding()) {
|
||||
boat.setVelocityDefault(false);
|
||||
if (sailsOut && boat.getCurrentSpeed() < goalVelocity) {
|
||||
boat.setCurrentSpeed(Math.min(goalVelocity, boat.getCurrentSpeed() + acceleration));
|
||||
} else if (!sailsOut && boat.getCurrentSpeed() > goalVelocity) {
|
||||
// Apply deceleration to strictly 0 speed
|
||||
boat.setCurrentSpeed(Math.max(0, boat.getCurrentSpeed() - acceleration));
|
||||
} else {
|
||||
// Release boat from SailsCommand control
|
||||
if (sailsOut) boat.setVelocityDefault(true);
|
||||
race.deleteObserver(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ public class TackGybeCommand extends ObserverCommand {
|
||||
*/
|
||||
public TackGybeCommand(MockRace race, MockBoat boat) {
|
||||
super(race, boat);
|
||||
race.addAngularCommand(this);
|
||||
race.addAngularCommand(this, boat.getSourceID());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -24,7 +24,7 @@ public class VMGCommand extends ObserverCommand {
|
||||
*/
|
||||
public VMGCommand(MockRace race, MockBoat boat) {
|
||||
super(race, boat);
|
||||
race.addAngularCommand(this);
|
||||
race.addAngularCommand(this, boat.getSourceID());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -20,7 +20,7 @@ public class WindCommand extends ObserverCommand {
|
||||
*/
|
||||
public WindCommand(MockRace race, MockBoat boat, boolean upwind) {
|
||||
super(race, boat);
|
||||
race.addAngularCommand(this);
|
||||
race.addAngularCommand(this, boat.getSourceID());
|
||||
this.direction = upwind? -1 : 1;
|
||||
}
|
||||
|
||||
|
||||
@@ -21,6 +21,8 @@ import java.time.ZonedDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Helper Class for creating a Race XML
|
||||
@@ -56,13 +58,12 @@ public class RaceXMLCreator {
|
||||
* Rotates the race in a specified direction.
|
||||
* @param s xml file name or contents.
|
||||
* @param fileType Whether s is a file name or contents.
|
||||
* @param degrees degrees to rotate
|
||||
* @param tutorial Whether we wish to run the tutorial - this changes the race start time.
|
||||
* @param degrees degrees to rotate. -1 means don't rotate.
|
||||
* @return the new xml file as a string
|
||||
* @throws XMLReaderException if the xml is not readable
|
||||
* @throws InvalidRaceDataException if the race is invalid
|
||||
*/
|
||||
public static String alterRaceToWind(String s, XMLFileType fileType, double degrees, boolean tutorial) throws XMLReaderException, InvalidRaceDataException {
|
||||
public static String alterRaceToWind(String s, XMLFileType fileType, double degrees) throws XMLReaderException, InvalidRaceDataException {
|
||||
|
||||
RaceXMLReader reader = new RaceXMLReader(s, fileType);
|
||||
|
||||
@@ -73,11 +74,6 @@ public class RaceXMLCreator {
|
||||
RaceXMLCreator.class.getClassLoader().getResource("mock/mockXML/schema/raceSchema.xsd"),
|
||||
XMLRace.class);
|
||||
|
||||
if(tutorial){
|
||||
setRaceXMLAtCurrentTimeToNow(race, 1000l, 5000l);
|
||||
} else {
|
||||
setRaceXMLAtCurrentTimeToNow(race);
|
||||
}
|
||||
|
||||
|
||||
CompoundMark leewardGate = getLeewardGate(reader);
|
||||
@@ -181,10 +177,10 @@ public class RaceXMLCreator {
|
||||
}
|
||||
|
||||
/**
|
||||
* gets the destance between two marks
|
||||
* gets the destance between two marks, in nautical miles.
|
||||
* @param a mark 1
|
||||
* @param b mark 2
|
||||
* @return
|
||||
* @return Distance in nautical miles.
|
||||
*/
|
||||
private static double getDistance(XMLMark a, XMLMark b){
|
||||
GPSCoordinate coorda = new GPSCoordinate(a.getTargetLat(), a.getTargetLng());
|
||||
@@ -218,12 +214,16 @@ public class RaceXMLCreator {
|
||||
double averageSpeed = (bestDownWindSpeed + bestUpWindSpeed) / 2;
|
||||
double raceApproximateTime = getRaceLength(race, averageSpeed);
|
||||
double scale = milliseconds / raceApproximateTime;
|
||||
Map<XMLCompoundMark, Boolean> hasBeenScaled = new HashMap<>();
|
||||
for (XMLCorner cm: race.getCompoundMarkSequence().getCorner()){
|
||||
int index = cm.getCompoundMarkID() - 1;
|
||||
XMLCompoundMark mark = race.getCourse().getCompoundMark().get(index);
|
||||
for (XMLMark m: mark.getMark()){
|
||||
scalePoint(m, center, scale);
|
||||
if (!hasBeenScaled.containsKey(mark)) {
|
||||
for (XMLMark m : mark.getMark()) {
|
||||
scalePoint(m, center, scale);
|
||||
}
|
||||
}
|
||||
hasBeenScaled.put(mark, true);
|
||||
}
|
||||
for (XMLLimit limit: race.getCourseLimit().getLimit()){
|
||||
scalePoint(limit, center, scale);
|
||||
@@ -325,8 +325,13 @@ public class RaceXMLCreator {
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* sets the current race time of the xml
|
||||
* @param raceXML race xml to alter
|
||||
* @param racePrestartTime prestart time
|
||||
* @param racePreparatoryTime preparatory time
|
||||
* @deprecated this should be used from the RaceXMLCreator not from this function
|
||||
*/
|
||||
public static void setRaceXMLAtCurrentTimeToNow(XMLRace raceXML, long racePrestartTime, long racePreparatoryTime){
|
||||
//The start time is current time + 4 minutes. prestart is 3 minutes, and we add another minute.
|
||||
long millisecondsToAdd = racePrestartTime + racePreparatoryTime;
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
package network.MessageDecoders;
|
||||
|
||||
import network.Exceptions.InvalidMessageException;
|
||||
import network.Messages.AC35Data;
|
||||
import network.Messages.BoatState;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import static network.Utils.ByteConverter.bytesToInt;
|
||||
|
||||
/**
|
||||
* Decoder for {@link BoatState} messages
|
||||
*/
|
||||
public class BoatStateDecoder implements MessageDecoder {
|
||||
/**
|
||||
* Decoded BoatState message
|
||||
*/
|
||||
private BoatState message;
|
||||
|
||||
@Override
|
||||
public AC35Data decode(byte[] encodedMessage) throws InvalidMessageException {
|
||||
byte[] sourceID = Arrays.copyOfRange(encodedMessage, 0, 4);
|
||||
byte boatHealth = encodedMessage[4];
|
||||
|
||||
// Unpack bytes into BoatState
|
||||
this.message = new BoatState(
|
||||
bytesToInt(sourceID),
|
||||
boatHealth
|
||||
);
|
||||
|
||||
// Return BoatState
|
||||
return this.message;
|
||||
}
|
||||
|
||||
public BoatState getMessage() {
|
||||
return message;
|
||||
}
|
||||
}
|
||||
@@ -59,6 +59,7 @@ public class DecoderFactory {
|
||||
|
||||
case BOATACTION: return new BoatActionDecoder();
|
||||
|
||||
case BOATSTATE: return new BoatStateDecoder();
|
||||
|
||||
default: throw new InvalidMessageTypeException("Unrecognised message type: " + type);
|
||||
}
|
||||
|
||||
@@ -18,6 +18,5 @@ public interface MessageDecoder {
|
||||
* @return The decoded message.
|
||||
* @throws InvalidMessageException Thrown if the encoded message is invalid in some way, or cannot be decoded.
|
||||
*/
|
||||
public AC35Data decode(byte[] encodedMessage) throws InvalidMessageException;
|
||||
|
||||
AC35Data decode(byte[] encodedMessage) throws InvalidMessageException;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
package network.MessageEncoders;
|
||||
|
||||
import network.Exceptions.InvalidMessageException;
|
||||
import network.Messages.AC35Data;
|
||||
import network.Messages.BoatState;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import static network.Utils.ByteConverter.intToBytes;
|
||||
|
||||
/**
|
||||
* Encoder for {@link BoatState} message
|
||||
*/
|
||||
public class BoatStateEncoder implements MessageEncoder {
|
||||
@Override
|
||||
public byte[] encode(AC35Data message) throws InvalidMessageException {
|
||||
// Downcast message
|
||||
BoatState boatState = (BoatState)message;
|
||||
|
||||
//Serialise message
|
||||
byte[] sourceID = intToBytes(boatState.getSourceID());
|
||||
byte boatHealth = (byte)boatState.getBoatHealth();
|
||||
|
||||
// Pack bytes into string
|
||||
ByteBuffer boatStateMessage = ByteBuffer.allocate(5);
|
||||
boatStateMessage.put(sourceID);
|
||||
boatStateMessage.put(boatHealth);
|
||||
|
||||
// Return byte string
|
||||
return boatStateMessage.array();
|
||||
}
|
||||
}
|
||||
@@ -59,6 +59,7 @@ public class EncoderFactory {
|
||||
|
||||
case BOATACTION: return new BoatActionEncoder();
|
||||
|
||||
case BOATSTATE: return new BoatStateEncoder();
|
||||
|
||||
default: throw new InvalidMessageTypeException("Unrecognised message type: " + type);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
package network.Messages;
|
||||
|
||||
import network.Messages.Enums.MessageType;
|
||||
|
||||
/**
|
||||
* Represents the information in a BoatState message according to protocol meeting
|
||||
*/
|
||||
public class BoatState extends AC35Data {
|
||||
/**
|
||||
* Source ID of boat described in message
|
||||
*/
|
||||
private int sourceID;
|
||||
/**
|
||||
* Health between 0-100 of boat with above source ID
|
||||
*/
|
||||
private int boatHealth;
|
||||
|
||||
public BoatState(int sourceID, int boatHealth) {
|
||||
super(MessageType.BOATSTATE);
|
||||
this.sourceID = sourceID;
|
||||
this.boatHealth = boatHealth;
|
||||
}
|
||||
|
||||
public int getSourceID() {
|
||||
return sourceID;
|
||||
}
|
||||
|
||||
public int getBoatHealth() {
|
||||
return boatHealth;
|
||||
}
|
||||
}
|
||||
@@ -43,6 +43,8 @@ public enum MessageType {
|
||||
|
||||
HOSTED_GAMES_REQUEST(109),
|
||||
|
||||
BOATSTATE(103),
|
||||
|
||||
NOTAMESSAGE(0);
|
||||
|
||||
|
||||
@@ -56,7 +58,7 @@ public enum MessageType {
|
||||
* Creates a MessageType enum from a given primitive integer value, cast to a byte.
|
||||
* @param value Integer, which is cast to byte, to construct from.
|
||||
*/
|
||||
private MessageType(int value) {
|
||||
MessageType(int value) {
|
||||
this.value = (byte)value;
|
||||
}
|
||||
|
||||
|
||||
@@ -151,4 +151,9 @@ public class EmptyRaceDataSource implements RaceDataSource {
|
||||
public void incrementSequenceNumber() {
|
||||
sequenceNumber++;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setStartDateTime(ZonedDateTime time) {
|
||||
raceStartTime = time;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,6 +67,12 @@ public interface RaceDataSource {
|
||||
*/
|
||||
ZonedDateTime getStartDateTime();
|
||||
|
||||
/**
|
||||
* Sets the start time/date of the race.
|
||||
* @param time Time to start at.
|
||||
*/
|
||||
void setStartDateTime(ZonedDateTime time);
|
||||
|
||||
/**
|
||||
* Returns the creation time/date of the race xml file.
|
||||
* @return The race xml file's creation time.
|
||||
|
||||
@@ -507,4 +507,9 @@ public class RaceXMLReader extends XMLReader implements RaceDataSource {
|
||||
public void incrementSequenceNumber() {
|
||||
sequenceNumber++;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setStartDateTime(ZonedDateTime time) {
|
||||
raceStartTime = time;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -103,6 +103,11 @@ public class Boat extends Collider {
|
||||
*/
|
||||
private boolean isColliding = false;
|
||||
|
||||
/**
|
||||
* Amount of health boat currently has, between 0 and 100
|
||||
*/
|
||||
private double health;
|
||||
|
||||
/**
|
||||
* Constructs a boat object with a given sourceID, name, country/team abbreviation, and polars table.
|
||||
*
|
||||
@@ -113,6 +118,7 @@ public class Boat extends Collider {
|
||||
public Boat(int sourceID, String name, String country) {
|
||||
|
||||
this.sourceID = sourceID;
|
||||
this.health = 100;
|
||||
this.setName(name);
|
||||
this.setCountry(country);
|
||||
|
||||
@@ -419,6 +425,8 @@ public class Boat extends Collider {
|
||||
@Override
|
||||
public void onCollisionEnter(Collision e) {
|
||||
if(e.getBearing().degrees() > 270 || e.getBearing().degrees() < 90) {
|
||||
// Deplete health
|
||||
e.getBoat().updateHealth(-5);
|
||||
// Notify observers of collision
|
||||
this.setChanged();
|
||||
notifyObservers(e);
|
||||
@@ -432,4 +440,22 @@ public class Boat extends Collider {
|
||||
public void setColliding(boolean colliding) {
|
||||
isColliding = colliding;
|
||||
}
|
||||
|
||||
public double getHealth() {
|
||||
return health;
|
||||
}
|
||||
|
||||
public void setHealth(double health) {
|
||||
this.health = health;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a given amount of HP to boat health
|
||||
* @param delta amount of HP to add
|
||||
*/
|
||||
public void updateHealth(double delta) {
|
||||
health += delta;
|
||||
if(health < 0) health = 0;
|
||||
else if(health > 100) health = 100;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ public class Constants {
|
||||
* Frame periods are multiplied by this to get the amount of time a single frame represents.
|
||||
* E.g., frame period = 20ms, scale = 5, frame represents 20 * 5 = 100ms, and so boats are simulated for 100ms, even though only 20ms actually occurred.
|
||||
*/
|
||||
public static final int RaceTimeScale = 2;
|
||||
public static final int RaceTimeScale = 1;
|
||||
|
||||
/**
|
||||
* The race pre-start time, in milliseconds. 30 seconds.
|
||||
|
||||
@@ -97,6 +97,7 @@ public class Mark extends Collider{
|
||||
|
||||
@Override
|
||||
public void onCollisionEnter(Collision e) {
|
||||
e.getBoat().updateHealth(-10);
|
||||
this.setChanged();
|
||||
notifyObservers(e);
|
||||
}
|
||||
|
||||
@@ -17,8 +17,9 @@ public class MarkRoundingSequence {
|
||||
|
||||
/**
|
||||
* For each leg, mark rounding information.
|
||||
* Maps between leg number and rounding data.
|
||||
*/
|
||||
private Map<Leg, MarkRoundingData> roundingPoints;
|
||||
private Map<Integer, MarkRoundingData> roundingPoints;
|
||||
|
||||
|
||||
|
||||
@@ -34,7 +35,7 @@ public class MarkRoundingSequence {
|
||||
* @return Rounding points for leg.
|
||||
*/
|
||||
public MarkRoundingData getRoundingData(Leg leg) {
|
||||
return roundingPoints.get(leg);
|
||||
return roundingPoints.get(leg.getLegNumber());
|
||||
}
|
||||
|
||||
|
||||
@@ -139,7 +140,7 @@ public class MarkRoundingSequence {
|
||||
roundingData.setRoundCheck2Halfway(roundCheck2Halfway);
|
||||
|
||||
|
||||
this.roundingPoints.put(currentLeg, roundingData);
|
||||
this.roundingPoints.put(currentLeg.getLegNumber(), roundingData);
|
||||
|
||||
|
||||
//Rounding points:
|
||||
|
||||
@@ -68,11 +68,11 @@ public class RaceClock {
|
||||
/**
|
||||
* Format string used for duration before it has started.
|
||||
*/
|
||||
private String durationBeforeStartFormat = "Starting in: %02d:%02d:%02d";
|
||||
private String durationBeforeStartFormat = "%02d:%02d:%02d";
|
||||
/**
|
||||
* Format string used for duration once the race has started.
|
||||
*/
|
||||
private String durationAfterStartFormat = "Time: %02d:%02d:%02d";
|
||||
private String durationAfterStartFormat = "%02d:%02d:%02d";
|
||||
|
||||
|
||||
|
||||
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
package visualiser.Commands.VisualiserRaceCommands;
|
||||
|
||||
import mock.model.commandFactory.Command;
|
||||
import network.Messages.BoatState;
|
||||
import shared.exceptions.BoatNotFoundException;
|
||||
import visualiser.model.VisualiserBoat;
|
||||
import visualiser.model.VisualiserRaceState;
|
||||
|
||||
/**
|
||||
* Updates boats on visualiser when their health changes
|
||||
*/
|
||||
public class BoatStateCommand implements Command {
|
||||
private BoatState boatState;
|
||||
private VisualiserRaceState visualiserRace;
|
||||
|
||||
public BoatStateCommand(BoatState boatState, VisualiserRaceState visualiserRace) {
|
||||
this.boatState = boatState;
|
||||
this.visualiserRace = visualiserRace;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute() {
|
||||
try {
|
||||
VisualiserBoat boat = visualiserRace.getBoat(boatState.getSourceID());
|
||||
boat.setHealth(boatState.getBoatHealth());
|
||||
} catch (BoatNotFoundException e) {
|
||||
// Fail silently
|
||||
}
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -24,7 +24,6 @@ public class VisualiserRaceCommandFactory {
|
||||
switch (message.getType()) {
|
||||
|
||||
case BOATLOCATION:
|
||||
//System.out.println("Boat location received");
|
||||
return new BoatLocationCommand((BoatLocation) message, visualiserRace);
|
||||
|
||||
case RACESTATUS: return new RaceStatusCommand((RaceStatus) message, visualiserRace);
|
||||
@@ -36,7 +35,8 @@ public class VisualiserRaceCommandFactory {
|
||||
case YACHTEVENTCODE:
|
||||
return new BoatCollisionCommand((YachtEvent) message, visualiserRace);
|
||||
|
||||
|
||||
case BOATSTATE:
|
||||
return new BoatStateCommand((BoatState) message, visualiserRace);
|
||||
|
||||
default: throw new CommandConstructionException("Could not create VisualiserRaceCommand. Unrecognised or unsupported MessageType: " + message.getType());
|
||||
|
||||
|
||||
@@ -4,6 +4,8 @@ import javafx.application.Platform;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.control.Alert;
|
||||
import javafx.scene.control.ButtonType;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.Slider;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.image.ImageView;
|
||||
import mock.app.Event;
|
||||
@@ -28,8 +30,12 @@ import java.util.logging.Logger;
|
||||
*/
|
||||
public class HostGameController extends Controller {
|
||||
private @FXML ImageView mapImage;
|
||||
private @FXML Slider sliderLength;
|
||||
private @FXML Label lblLength;
|
||||
private ArrayList<Image> listOfMaps;
|
||||
private int currentMapIndex = 0;
|
||||
private int selectedRaceLength; // in minutes
|
||||
private final int MAX_RACE_LENGTH = 30; // in minutes
|
||||
private DatagramSocket udpSocket;
|
||||
private MatchBrowserInterface matchBrowserInterface;
|
||||
|
||||
@@ -37,8 +43,31 @@ public class HostGameController extends Controller {
|
||||
loadMaps();
|
||||
this.udpSocket = MatchBrowserSingleton.getInstance().getUdpSocket();
|
||||
this.matchBrowserInterface = MatchBrowserSingleton.getInstance().getMatchBrowserInterface();
|
||||
setRaceLengthSlider();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up the values and display for a slider object which allows a user
|
||||
* to select how many minutes long they would like their race to be.
|
||||
*/
|
||||
private void setRaceLengthSlider(){
|
||||
// set the listener to update the label
|
||||
sliderLength.valueProperty().addListener((ov, old_val, new_val) -> {
|
||||
selectedRaceLength = new_val.intValue();
|
||||
if (selectedRaceLength == 1){
|
||||
lblLength.setText(selectedRaceLength + " minute.");
|
||||
} else {
|
||||
lblLength.setText(selectedRaceLength + " minutes.");
|
||||
}
|
||||
});
|
||||
|
||||
// set values and marks to be displayed
|
||||
sliderLength.setMin(5);
|
||||
sliderLength.setMax(MAX_RACE_LENGTH);
|
||||
sliderLength.setShowTickLabels(true);
|
||||
sliderLength.setMajorTickUnit(MAX_RACE_LENGTH-1);
|
||||
sliderLength.setBlockIncrement(1);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
@@ -54,8 +83,8 @@ public class HostGameController extends Controller {
|
||||
listOfMaps = new ArrayList(Arrays.asList(ac35Map, oMap, iMap, mMap));
|
||||
mapImage.setImage(listOfMaps.get(currentMapIndex));
|
||||
Platform.runLater(() -> {
|
||||
mapImage.fitWidthProperty()
|
||||
.bind(mapImage.getScene().getWindow().widthProperty().multiply(0.6));
|
||||
mapImage.fitHeightProperty()
|
||||
.bind(mapImage.getScene().getWindow().heightProperty().multiply(0.6));
|
||||
});
|
||||
}
|
||||
|
||||
@@ -64,7 +93,8 @@ public class HostGameController extends Controller {
|
||||
*/
|
||||
public void hostGamePressed() {
|
||||
try {
|
||||
App.game = new Event(false, currentMapIndex);
|
||||
App.game = new Event(false, currentMapIndex,
|
||||
selectedRaceLength*60*1000);
|
||||
App.gameType = currentMapIndex;
|
||||
|
||||
HttpMatchBrowserHost matchBrowserHost = new HttpMatchBrowserHost();
|
||||
|
||||
@@ -12,25 +12,31 @@ import javafx.scene.AmbientLight;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.PointLight;
|
||||
import javafx.scene.control.Alert;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.ButtonType;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.effect.Light;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.scene.layout.AnchorPane;
|
||||
import javafx.scene.layout.GridPane;
|
||||
import javafx.scene.paint.Color;
|
||||
import javafx.scene.paint.PhongMaterial;
|
||||
import javafx.scene.shape.MeshView;
|
||||
import javafx.scene.shape.Shape3D;
|
||||
import mock.app.Event;
|
||||
import network.Messages.Enums.RaceStatusEnum;
|
||||
import network.Messages.Enums.RequestToJoinEnum;
|
||||
import shared.model.Boat;
|
||||
import visualiser.app.App;
|
||||
import visualiser.gameController.ControllerClient;
|
||||
import visualiser.layout.*;
|
||||
import visualiser.model.VisualiserBoat;
|
||||
import visualiser.model.VisualiserRaceEvent;
|
||||
import visualiser.model.VisualiserRaceState;
|
||||
import visualiser.network.HttpMatchBrowserHost;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.Socket;
|
||||
import java.net.URL;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
@@ -68,11 +74,13 @@ public class InGameLobbyController extends Controller {
|
||||
private Label playerLabel6;
|
||||
|
||||
@FXML
|
||||
private Label countdownLabel;
|
||||
private Button startButton;
|
||||
|
||||
@FXML
|
||||
private GridPane animatedPane;
|
||||
|
||||
@FXML
|
||||
private Button quitButton;
|
||||
|
||||
private Event game;
|
||||
|
||||
@@ -197,34 +205,54 @@ public class InGameLobbyController extends Controller {
|
||||
|
||||
for (VisualiserBoat boat : copy) {
|
||||
View3D playerBoatToSet = new View3D();
|
||||
|
||||
playerBoatToSet.setItems(subjects);
|
||||
|
||||
playerContainer.add(playerBoatToSet, (count % 3) , row);
|
||||
playerContainer.setMargin(playerBoatToSet, new Insets(10, 10, 10, 10));
|
||||
|
||||
SeaSurface sea = new SeaSurface(750, 200);
|
||||
sea.setX(250);
|
||||
sea.setZ(210);
|
||||
subjects.add(sea);
|
||||
// SeaSurface sea = new SeaSurface(750, 200);
|
||||
// sea.setX(250);
|
||||
// sea.setZ(210);
|
||||
// subjects.add(sea);
|
||||
//
|
||||
NewSeaSurface sea = new NewSeaSurface();
|
||||
Subject3D seaSubject = new Annotation3D(sea);
|
||||
seaSubject.setX(50);
|
||||
seaSubject.setZ(50);
|
||||
seaSubject.setXRot(0);
|
||||
subjects.add(seaSubject);
|
||||
|
||||
MeshView mesh = new MeshView(importer.getImport());
|
||||
// MeshView mesh = new MeshView(importer.getImport());
|
||||
// Subject3D subject = new Subject3D(mesh,0);
|
||||
// subjects.add(subject);
|
||||
Shape3D mesh = Assets3D.getBoat();
|
||||
PhongMaterial boatColorMat = new PhongMaterial(boat.getColor());
|
||||
mesh.setMaterial(boatColorMat);
|
||||
Subject3D subject = new Subject3D(mesh,0);
|
||||
//mesh.setMaterial(boatColorMat);
|
||||
Subject3D subject = new Subject3D(mesh, 0);
|
||||
subjects.add(subject);
|
||||
|
||||
Sails3D sails3D = new Sails3D();
|
||||
Subject3D sailsSubject = new Subject3D(sails3D, 0);
|
||||
sails3D.setMaterial(boatColorMat);
|
||||
subjects.add(sailsSubject);
|
||||
sailsSubject.setXRot(0d);
|
||||
//sails3D.startLuffing();
|
||||
|
||||
playerBoatToSet.setDistance(50);
|
||||
playerBoatToSet.setYaw(45);
|
||||
playerBoatToSet.setPitch(20);
|
||||
|
||||
|
||||
if (boat.isClientBoat()) {
|
||||
Shockwave boatHighlight = new Shockwave(10);
|
||||
boatHighlight.getMesh().setMaterial(new PhongMaterial(new Color(1, 1, 0, 0.1)));
|
||||
boatHighlight.setX(subject.getPosition().getX());
|
||||
boatHighlight.setY(subject.getPosition().getY());
|
||||
boatHighlight.setZ(subject.getPosition().getZ());
|
||||
subjects.add(boatHighlight);
|
||||
/*Shockwave boatHighlight = new Shockwave(10);
|
||||
boatHighlight.getMesh().setMaterial(new PhongMaterial(new Color(1, 1, 0, 0.1)));*/
|
||||
|
||||
Assets3D.boatHighlight.setX(subject.getPosition().getX());
|
||||
Assets3D.boatHighlight.setY(subject.getPosition().getY());
|
||||
Assets3D.boatHighlight.setZ(subject.getPosition().getZ());
|
||||
|
||||
subjects.add(Assets3D.boatHighlight);
|
||||
subject.getMesh().toFront();
|
||||
}
|
||||
|
||||
@@ -232,6 +260,9 @@ public class InGameLobbyController extends Controller {
|
||||
@Override
|
||||
public void handle(long now) {
|
||||
subject.setHeading(subject.getHeading().getAngle() + 0.1);
|
||||
sailsSubject.setHeading(subject.getHeading().getAngle() + 0.1);
|
||||
sailsSubject.setX(subject.getX());
|
||||
sailsSubject.setZ(subject.getZ());
|
||||
}
|
||||
};
|
||||
rotate.start();
|
||||
@@ -258,37 +289,13 @@ public class InGameLobbyController extends Controller {
|
||||
* Starts the race.
|
||||
*/
|
||||
private void startRace() {
|
||||
//Initialises the race clock.
|
||||
initialiseRaceClock(this.visualiserRaceEvent.getVisualiserRaceState());
|
||||
|
||||
//Starts the race countdown timer.
|
||||
countdownTimer();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialises the race clock/timer labels for the start time, current time, and remaining time.
|
||||
* @param visualiserRace The race to get data from.
|
||||
*/
|
||||
private void initialiseRaceClock(VisualiserRaceState visualiserRace) {
|
||||
//Remaining time.
|
||||
initialiseRaceClockDuration(visualiserRace);
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Initialises the race duration label.
|
||||
* @param visualiserRace The race to get data from.
|
||||
*/
|
||||
private void initialiseRaceClockDuration(VisualiserRaceState visualiserRace) {
|
||||
|
||||
visualiserRace.getRaceClock().durationProperty().addListener((observable, oldValue, newValue) -> {
|
||||
Platform.runLater(() -> {
|
||||
countdownLabel.setText(newValue);
|
||||
});
|
||||
});
|
||||
|
||||
}
|
||||
/**
|
||||
* Countdown timer until race starts.
|
||||
*/
|
||||
@@ -299,6 +306,7 @@ public class InGameLobbyController extends Controller {
|
||||
//Get the current race status.
|
||||
RaceStatusEnum raceStatus = visualiserRaceEvent.getVisualiserRaceState().getRaceStatusEnum();
|
||||
|
||||
|
||||
//If the race has reached the preparatory phase, or has started...
|
||||
if (raceStatus == RaceStatusEnum.PREPARATORY || raceStatus == RaceStatusEnum.STARTED) {
|
||||
//Stop this timer.
|
||||
@@ -336,6 +344,8 @@ public class InGameLobbyController extends Controller {
|
||||
|
||||
this.visualiserRaceEvent.getVisualiserRaceState().getBoats().addListener(this.lobbyUpdateListener);
|
||||
|
||||
enableStartIfHost();
|
||||
|
||||
startRace();
|
||||
} catch (IOException e) {
|
||||
//TODO should probably let this propagate, so that we only enter this scene if everything works
|
||||
@@ -343,6 +353,22 @@ public class InGameLobbyController extends Controller {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Enables the start button if the client is the host of the game.
|
||||
*/
|
||||
private void enableStartIfHost() {
|
||||
if (isHost) {
|
||||
startButton.setVisible(true);
|
||||
startButton.setDisable(false);
|
||||
} else {
|
||||
startButton.setVisible(false);
|
||||
startButton.setDisable(true);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Menu button pressed. Prompt alert then return to menu
|
||||
* @throws IOException socket erro
|
||||
@@ -370,7 +396,12 @@ public class InGameLobbyController extends Controller {
|
||||
* Start button pressed. Currently only prints out start
|
||||
*/
|
||||
public void startBtnPressed(){
|
||||
//System.out.println("Should start the race. This button is only visible for the host");
|
||||
try {
|
||||
HttpMatchBrowserHost.httpMatchBrowserHost.sendStarted();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
App.game.getRaceLogic().getRace().startRace(App.game.getRaceLogic().getRace().getRacePreparatoryTime(), true);
|
||||
}
|
||||
|
||||
public void joinSpecPressed(){
|
||||
|
||||
@@ -40,7 +40,7 @@ public class LobbyController extends Controller {
|
||||
private @FXML TextField addressFld;
|
||||
private @FXML TextField portFld;
|
||||
|
||||
private ObservableList<RaceConnection> connections;
|
||||
private ObservableList<RaceConnection> allConnections;
|
||||
private ObservableList<RaceConnection> customConnections;
|
||||
|
||||
private AudioClip sound;
|
||||
@@ -51,12 +51,20 @@ public class LobbyController extends Controller {
|
||||
|
||||
public void initialize() {
|
||||
httpMatchBrowserClient = new HttpMatchBrowserClient();
|
||||
httpMatchBrowserClient.connections.addListener(new ListChangeListener<RaceConnection>() {
|
||||
@Override
|
||||
public void onChanged(Change<? extends RaceConnection> c) {
|
||||
refreshTable();
|
||||
}
|
||||
});
|
||||
|
||||
new Thread(httpMatchBrowserClient, "Match Client").start();
|
||||
// set up the connection table
|
||||
customConnections = FXCollections.observableArrayList();
|
||||
allConnections = FXCollections.observableArrayList();
|
||||
//connections.add(new RaceConnection("localhost", 4942, "Local Game"));
|
||||
|
||||
lobbyTable.setItems(httpMatchBrowserClient.connections);
|
||||
lobbyTable.setItems(allConnections);
|
||||
gameNameColumn.setCellValueFactory(cellData -> cellData.getValue().gamenameProperty());
|
||||
hostNameColumn.setCellValueFactory(cellData -> cellData.getValue().hostnameProperty());
|
||||
statusColumn.setCellValueFactory(cellData -> cellData.getValue().statusProperty());
|
||||
@@ -82,8 +90,14 @@ public class LobbyController extends Controller {
|
||||
public void refreshBtnPressed(){
|
||||
sound = new AudioClip(this.getClass().getResource("/visualiser/sounds/buttonpress.wav").toExternalForm());
|
||||
sound.play();
|
||||
refreshTable();
|
||||
}
|
||||
|
||||
private void refreshTable() {
|
||||
allConnections.clear();
|
||||
addCustomGames();
|
||||
addServerGames();
|
||||
for(RaceConnection connection: connections) {
|
||||
for(RaceConnection connection: allConnections) {
|
||||
connection.check();
|
||||
}
|
||||
try {
|
||||
@@ -144,9 +158,9 @@ public class LobbyController extends Controller {
|
||||
try {
|
||||
int port = Integer.parseInt(portString);
|
||||
customConnections.add(new RaceConnection(hostName, port, "Boat Game"));
|
||||
connections.addAll(customConnections);
|
||||
addressFld.clear();
|
||||
portFld.clear();
|
||||
refreshTable();
|
||||
} catch (NumberFormatException e) {
|
||||
System.err.println("Port number entered is not a number");
|
||||
}
|
||||
@@ -173,17 +187,14 @@ public class LobbyController extends Controller {
|
||||
* Adds the games received from the server
|
||||
*/
|
||||
private void addServerGames() {
|
||||
httpMatchBrowserClient.connections.addAll(customConnections);
|
||||
httpMatchBrowserClient.connections.addListener(new ListChangeListener<RaceConnection>() {
|
||||
@Override
|
||||
public void onChanged(Change<? extends RaceConnection> c) {
|
||||
refreshBtnPressed();
|
||||
}
|
||||
});
|
||||
|
||||
allConnections.addAll(httpMatchBrowserClient.connections);
|
||||
/*
|
||||
for (HostGame game : matchBrowserLobbyInterface.getGames()) {
|
||||
connections.add(new RaceConnection(game.getIp(), 4942, "Boat Game"));
|
||||
}*/
|
||||
}
|
||||
|
||||
private void addCustomGames() {
|
||||
allConnections.addAll(customConnections);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,123 @@
|
||||
package visualiser.Controllers;
|
||||
|
||||
import com.interactivemesh.jfx.importer.stl.StlMeshImporter;
|
||||
import javafx.animation.AnimationTimer;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.geometry.Point3D;
|
||||
import javafx.scene.AmbientLight;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.layout.Pane;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.paint.Color;
|
||||
import javafx.scene.paint.PhongMaterial;
|
||||
import javafx.scene.shape.Cylinder;
|
||||
import javafx.scene.shape.MeshView;
|
||||
import javafx.scene.shape.Shape3D;
|
||||
import javafx.scene.transform.Rotate;
|
||||
import shared.model.Bearing;
|
||||
import shared.model.CompoundMark;
|
||||
import shared.model.GPSCoordinate;
|
||||
import visualiser.layout.Annotation3D;
|
||||
import visualiser.layout.Assets3D;
|
||||
import visualiser.layout.Subject3D;
|
||||
import visualiser.layout.View3D;
|
||||
import visualiser.model.VisualiserBoat;
|
||||
|
||||
import java.net.URL;
|
||||
import java.util.Observable;
|
||||
import java.util.Observer;
|
||||
|
||||
public class NextMarkController {
|
||||
private @FXML StackPane arrowStackPane2d;
|
||||
private @FXML StackPane arrowStackPane3d;
|
||||
private @FXML Pane pane2d;
|
||||
private @FXML Pane pane3d;
|
||||
|
||||
private VisualiserBoat boat;
|
||||
|
||||
public void initialiseArrowView(VisualiserBoat boat) {
|
||||
this.boat = boat;
|
||||
pane2d.setVisible(true);
|
||||
pane3d.setVisible(false);
|
||||
initialise2dArrowView();
|
||||
initialise3dArrowView();
|
||||
}
|
||||
|
||||
private void initialise2dArrowView() {
|
||||
AnimationTimer arrow2d = new AnimationTimer() {
|
||||
@Override
|
||||
public void handle(long now) {
|
||||
if (boat.getCurrentLeg().getEndCompoundMark() != null) {
|
||||
CompoundMark target = boat.getCurrentLeg().getEndCompoundMark();
|
||||
Bearing headingToMark = GPSCoordinate.calculateBearing(boat.getPosition(), target.getAverageGPSCoordinate());
|
||||
arrowStackPane2d.setRotate(headingToMark.degrees());
|
||||
} else {
|
||||
stop();
|
||||
}
|
||||
}
|
||||
};
|
||||
arrow2d.start();
|
||||
}
|
||||
|
||||
private void initialise3dArrowView() {
|
||||
ObservableList<Subject3D> viewSubjects = FXCollections.observableArrayList();
|
||||
String arrowPath = "assets/mark_arrow.x3d";
|
||||
|
||||
Shape3D arrow = Assets3D.loadX3d(arrowPath);
|
||||
|
||||
arrow.setScaleX(15);
|
||||
arrow.setScaleY(25);
|
||||
arrow.setScaleZ(50);
|
||||
arrow.setRotationAxis(new Point3D(1,0,0));
|
||||
|
||||
arrowStackPane3d.getChildren().add(arrow);
|
||||
|
||||
AnimationTimer arrow3d = new AnimationTimer() {
|
||||
@Override
|
||||
public void handle(long now) {
|
||||
if (boat.getCurrentLeg().getEndCompoundMark() != null) {
|
||||
arrow.getTransforms().clear();
|
||||
double zRotation = calculateZRotate();
|
||||
arrow.setRotate(calculateXRotate(zRotation));
|
||||
arrow.getTransforms().add(new Rotate(zRotation, new Point3D(0, 0, 1)));
|
||||
} else {
|
||||
stop();
|
||||
}
|
||||
}
|
||||
};
|
||||
arrow3d.start();
|
||||
}
|
||||
|
||||
public void show2d() {
|
||||
pane3d.setVisible(false);
|
||||
pane2d.setVisible(true);
|
||||
}
|
||||
|
||||
public void show3d() {
|
||||
pane2d.setVisible(false);
|
||||
pane3d.setVisible(true);
|
||||
}
|
||||
|
||||
private double calculateZRotate() {
|
||||
CompoundMark target = boat.getCurrentLeg().getEndCompoundMark();
|
||||
Bearing headingToMark = GPSCoordinate.calculateBearing(boat.getPosition(), target.getAverageGPSCoordinate());
|
||||
return -headingToMark.degrees() + boat.getBearing().degrees() + 180;
|
||||
}
|
||||
|
||||
private double calculateXRotate(double zRotation) {
|
||||
// if (zRotation > 360) {
|
||||
// zRotation -=360;
|
||||
// } else if (zRotation < 0) {
|
||||
// zRotation += 360;
|
||||
// }
|
||||
//
|
||||
// if (zRotation > 180) {
|
||||
// zRotation = 360 - zRotation;
|
||||
// }
|
||||
|
||||
return 70;
|
||||
//return 90 - 20 * Math.cos(Math.toRadians(zRotation));
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
package visualiser.Controllers;
|
||||
|
||||
import com.interactivemesh.jfx.importer.stl.StlMeshImporter;
|
||||
import eu.hansolo.medusa.*;
|
||||
import javafx.animation.AnimationTimer;
|
||||
import javafx.application.Platform;
|
||||
import javafx.collections.FXCollections;
|
||||
@@ -12,20 +13,29 @@ import javafx.scene.AmbientLight;
|
||||
import javafx.scene.PointLight;
|
||||
import javafx.scene.chart.LineChart;
|
||||
import javafx.scene.control.*;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.scene.input.KeyCode;
|
||||
import javafx.scene.input.KeyEvent;
|
||||
import javafx.scene.layout.AnchorPane;
|
||||
import javafx.scene.layout.GridPane;
|
||||
import javafx.scene.layout.Pane;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.media.AudioClip;
|
||||
import javafx.scene.paint.Color;
|
||||
import javafx.scene.paint.Material;
|
||||
import javafx.scene.paint.PhongMaterial;
|
||||
import javafx.scene.shape.Cylinder;
|
||||
import javafx.scene.paint.Stop;
|
||||
import javafx.scene.shape.MeshView;
|
||||
import javafx.scene.shape.Shape3D;
|
||||
import javafx.scene.shape.Sphere;
|
||||
import javafx.scene.transform.Translate;
|
||||
import javafx.util.Callback;
|
||||
import network.Messages.Enums.BoatStatusEnum;
|
||||
import network.Messages.Enums.RaceStatusEnum;
|
||||
import shared.dataInput.RaceDataSource;
|
||||
import shared.enums.RoundingType;
|
||||
import shared.exceptions.BoatNotFoundException;
|
||||
import shared.model.*;
|
||||
import visualiser.app.App;
|
||||
@@ -38,11 +48,9 @@ import visualiser.model.*;
|
||||
import visualiser.utils.GPSConverter;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.URL;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.*;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
@@ -61,9 +69,17 @@ public class RaceViewController extends Controller {
|
||||
private boolean isTutorial = false;
|
||||
private String keyToPress;
|
||||
private View3D view3D;
|
||||
private WindCompass windCompass;
|
||||
private ObservableList<Subject3D> viewSubjects;
|
||||
private Gauge gauge;
|
||||
private FGauge fGauge;
|
||||
private long positionDelay = 1000;
|
||||
private long positionTime = 0;
|
||||
private ResizableRaceCanvas raceCanvas;
|
||||
private boolean mapToggle = true;
|
||||
private GPSConverter gpsConverter;
|
||||
private ArrayList<HealthEffect> healthEffectList = new ArrayList<>();
|
||||
|
||||
|
||||
/**
|
||||
* Arrow pointing to next mark in third person
|
||||
@@ -74,12 +90,22 @@ public class RaceViewController extends Controller {
|
||||
*/
|
||||
private AnimationTimer pointToMark;
|
||||
|
||||
//seagulls
|
||||
private ObservableList<Subject3D> seagulls = FXCollections.observableArrayList();
|
||||
//seagulls goto
|
||||
private Map<Subject3D, List<Double>> seagullsGoToX = new HashMap<>();
|
||||
private Map<Subject3D, List<Double>> seagullsGoToY = new HashMap<>();
|
||||
private double seagullSpeed = 0.01;
|
||||
|
||||
// note: it says it's not used but it is! do not remove :)
|
||||
private @FXML ArrowController arrowController;
|
||||
private @FXML NextMarkController nextMarkController;
|
||||
private @FXML GridPane canvasBase;
|
||||
private @FXML GridPane canvasBase1;
|
||||
private @FXML GridPane canvasBase2;
|
||||
private @FXML SplitPane racePane;
|
||||
private @FXML StackPane arrowPane;
|
||||
private @FXML Pane nextMarkPane;
|
||||
private @FXML Label timer;
|
||||
private @FXML Label FPS;
|
||||
private @FXML Label timeZone;
|
||||
@@ -89,10 +115,17 @@ public class RaceViewController extends Controller {
|
||||
private @FXML TableColumn<VisualiserBoat, String> boatTeamColumn;
|
||||
private @FXML TableColumn<VisualiserBoat, Leg> boatMarkColumn;
|
||||
private @FXML TableColumn<VisualiserBoat, Number> boatSpeedColumn;
|
||||
private @FXML TableColumn<VisualiserBoat, Number> boatHealthColumn;
|
||||
private @FXML LineChart<Number, Number> sparklineChart;
|
||||
private @FXML Label tutorialText;
|
||||
private @FXML AnchorPane infoWrapper;
|
||||
private @FXML AnchorPane lineChartWrapper;
|
||||
private @FXML StackPane speedPane;
|
||||
private @FXML AnchorPane raceAnchorPane;
|
||||
private @FXML GridPane playerHealthContainer;
|
||||
private @FXML ImageView imageView;
|
||||
private @FXML AnchorPane deathTransPane;
|
||||
private @FXML StackPane deathPane;
|
||||
|
||||
/**
|
||||
* Displays a specified race.
|
||||
@@ -107,11 +140,13 @@ public class RaceViewController extends Controller {
|
||||
this.controllerClient = controllerClient;
|
||||
this.isHost = isHost;
|
||||
keyFactory.load();
|
||||
|
||||
deathPane.setDisable(false);
|
||||
deathPane.setVisible(false);
|
||||
tutorialCheck();
|
||||
initKeypressHandler();
|
||||
healthLoop();
|
||||
initialiseRaceVisuals();
|
||||
initialiseRaceCanvas();
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -123,7 +158,6 @@ public class RaceViewController extends Controller {
|
||||
isTutorial = true;
|
||||
tutorialText.setVisible(true);
|
||||
tutorialStates = new ArrayList<>(Arrays.asList(TutorialState.values()));
|
||||
|
||||
currentState = tutorialStates.get(0);
|
||||
tutorialStates.remove(0);
|
||||
searchMapForKey("Upwind");
|
||||
@@ -204,37 +238,122 @@ public class RaceViewController extends Controller {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Create speedometer
|
||||
*/
|
||||
private void initialiseSpeedometer() {
|
||||
//Create the Medusa Gauge
|
||||
gauge = GaugeBuilder.create()
|
||||
.prefSize(200,200)
|
||||
.foregroundBaseColor(Color.WHITE)
|
||||
.title("Title")
|
||||
.subTitle("Speed")
|
||||
.unit("Knots")
|
||||
.decimals(2)
|
||||
.lcdVisible(true)
|
||||
.lcdDesign(LcdDesign.STANDARD)
|
||||
.lcdFont(LcdFont.DIGITAL_BOLD)
|
||||
.scaleDirection(Gauge.ScaleDirection.CLOCKWISE)
|
||||
.minValue(0)
|
||||
.maxValue(50)
|
||||
.startAngle(320)
|
||||
.angleRange(280)
|
||||
.tickLabelDecimals(0)
|
||||
.tickLabelLocation(TickLabelLocation.INSIDE)
|
||||
.tickLabelOrientation(TickLabelOrientation.ORTHOGONAL)
|
||||
.onlyFirstAndLastTickLabelVisible(false)
|
||||
.tickLabelSectionsVisible(false)
|
||||
.tickLabelColor(Color.BLACK)
|
||||
.tickMarkSectionsVisible(false)
|
||||
.majorTickMarksVisible(true)
|
||||
.majorTickMarkType(TickMarkType.TRAPEZOID)
|
||||
.mediumTickMarksVisible(false)
|
||||
.mediumTickMarkType(TickMarkType.LINE)
|
||||
.minorTickMarksVisible(true)
|
||||
.minorTickMarkType(TickMarkType.LINE)
|
||||
.ledVisible(false)
|
||||
.ledType(Gauge.LedType.STANDARD)
|
||||
.ledColor(Color.rgb(255, 200, 0))
|
||||
.ledBlinking(false)
|
||||
.needleShape(Gauge.NeedleShape.ANGLED)
|
||||
.needleSize(Gauge.NeedleSize.STANDARD)
|
||||
.needleColor(Color.CRIMSON)
|
||||
.startFromZero(false)
|
||||
.returnToZero(false)
|
||||
.knobType(Gauge.KnobType.METAL)
|
||||
.knobColor(Color.LIGHTGRAY)
|
||||
.interactive(false)
|
||||
.thresholdVisible(true)
|
||||
.threshold(50)
|
||||
.thresholdColor(Color.RED)
|
||||
.checkThreshold(true)
|
||||
.gradientBarEnabled(true)
|
||||
.gradientBarStops(new Stop(0.0, Color.BLUE),
|
||||
new Stop(0.25, Color.CYAN),
|
||||
new Stop(0.5, Color.LIME),
|
||||
new Stop(0.75, Color.YELLOW),
|
||||
new Stop(1.0, Color.RED))
|
||||
.markersVisible(true)
|
||||
.build();
|
||||
|
||||
|
||||
//Create a gauge with a frame and background that utilizes a Medusa gauge
|
||||
fGauge = FGaugeBuilder
|
||||
.create()
|
||||
.prefSize(190, 190)
|
||||
.gauge(gauge)
|
||||
.gaugeDesign(GaugeDesign.METAL)
|
||||
.gaugeBackground(GaugeDesign.GaugeBackground.CARBON)
|
||||
.foregroundVisible(true)
|
||||
.build();
|
||||
speedPane.getChildren().add(fGauge);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialises the various UI components to listen to the {@link #visualiserRace}.
|
||||
*/
|
||||
private void initialiseRaceVisuals() {
|
||||
|
||||
// Import arrow mesh
|
||||
URL asset = this.getClass().getClassLoader().getResource("assets/arrow V1.0.4.stl");
|
||||
StlMeshImporter importer = new StlMeshImporter();
|
||||
importer.read(asset);
|
||||
|
||||
MeshView arrow = new MeshView(importer.getImport());
|
||||
PhongMaterial arrowMat = new PhongMaterial(Color.RED);
|
||||
arrow.setMaterial(arrowMat);
|
||||
|
||||
this.nextMarkArrow = new Annotation3D(arrow);
|
||||
this.nextMarkArrow.setScale(0.1);
|
||||
|
||||
// initialise displays
|
||||
initialiseFps();
|
||||
initialiseInfoTable();
|
||||
initialiseView3D(this.visualiserRace);
|
||||
initialiseHealthPane();
|
||||
initialiseRaceClock();
|
||||
initialiseSpeedometer();
|
||||
initialiseRaceCanvas();
|
||||
raceTimer(); // start the timer
|
||||
//nextMarkPane.toFront();
|
||||
speedometerLoop();
|
||||
new Sparkline(this.raceState, this.sparklineChart);
|
||||
timeZone.setText(this.raceState.getRaceClock().getTimeZone());
|
||||
arrowController.setWindProperty(this.raceState.windProperty());
|
||||
}
|
||||
|
||||
|
||||
|
||||
private void initialiseHealthPane() {
|
||||
InputStream tomato = this.getClass().getClassLoader().getResourceAsStream("visualiser/images/tomato.png");
|
||||
HealthSlider healthSlider = new HealthSlider(new Image(tomato));
|
||||
playerHealthContainer.add(healthSlider, 0, 0);
|
||||
|
||||
try {
|
||||
VisualiserBoat player = raceState.getBoat(raceState.getPlayerBoatID());
|
||||
player.healthProperty().addListener((o, prev, curr) -> {
|
||||
healthSlider.setCrop((double)curr/100.0);
|
||||
});
|
||||
} catch (BoatNotFoundException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
private void initialiseView3D(VisualiserRaceEvent race) {
|
||||
viewSubjects = FXCollections.observableArrayList();
|
||||
|
||||
try {
|
||||
nextMarkController.initialiseArrowView(race.getVisualiserRaceState().getBoat(race.getVisualiserRaceState().getPlayerBoatID()));
|
||||
} catch (BoatNotFoundException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
AmbientLight ambientLight = new AmbientLight(Color.web("#CCCCFF"));
|
||||
ambientLight.setTranslateX(250);
|
||||
ambientLight.setTranslateZ(210);
|
||||
@@ -268,24 +387,46 @@ public class RaceViewController extends Controller {
|
||||
view3D.addPointLight(pointLight);
|
||||
canvasBase.add(view3D, 0, 0);
|
||||
|
||||
windCompass = new WindCompass(view3D, this.raceState.windProperty());
|
||||
arrowPane.getChildren().add(windCompass);
|
||||
|
||||
// Set up projection from GPS to view
|
||||
RaceDataSource raceData = visualiserRace.getVisualiserRaceState().getRaceDataSource();
|
||||
final GPSConverter gpsConverter = new GPSConverter(raceData, 450, 450);
|
||||
gpsConverter = new GPSConverter(raceData, 450, 450);
|
||||
|
||||
SkyBox skyBox = new SkyBox(750, 200, 250, 0, 210);
|
||||
SkyBox skyBox = Assets3D.skyBox;
|
||||
viewSubjects.addAll(skyBox.getSkyBoxPlanes());
|
||||
|
||||
// Set up sea surface
|
||||
SeaSurface sea = new SeaSurface(750, 200);
|
||||
sea.setX(250);
|
||||
sea.setZ(210);
|
||||
viewSubjects.add(sea);
|
||||
// SeaSurface sea = new SeaSurface(750, 200);
|
||||
// sea.setX(250);
|
||||
// sea.setZ(210);
|
||||
// viewSubjects.add(sea);
|
||||
|
||||
// Set up sea surface overlay
|
||||
SeaSurface seaOverlay = new SeaSurface(4000, 200);
|
||||
seaOverlay.setX(250);
|
||||
seaOverlay.setZ(210);
|
||||
viewSubjects.add(seaOverlay);
|
||||
// SeaSurface seaOverlay = new SeaSurface(4000, 200);
|
||||
// seaOverlay.setX(250);
|
||||
// seaOverlay.setZ(210);
|
||||
// viewSubjects.add(seaOverlay);
|
||||
// int seaX = 15;
|
||||
// int seaY = 15;
|
||||
int seaX = 15;
|
||||
int seaY = 15;
|
||||
for (int x = 0; x < seaX; x++) {
|
||||
for (int y = 0; y < seaY; y++) {
|
||||
NewSeaSurface seaSurface = new NewSeaSurface();
|
||||
Subject3D seaSubject = new Annotation3D(seaSurface);
|
||||
seaSubject.setXRot(0);
|
||||
// seaSubject.setX(-75 + x * 50);
|
||||
// seaSubject.setZ(-150 + y * 50);
|
||||
// seaSubject.setX(-150 + x * 250);
|
||||
// seaSubject.setZ(-80 + y * 250);
|
||||
seaSubject.setX(-75 + x * 100);
|
||||
seaSubject.setZ(-150 + y * 100);
|
||||
seaSubject.setY(3);
|
||||
viewSubjects.add(seaSubject);
|
||||
}
|
||||
}
|
||||
|
||||
Boundary3D boundary3D = new Boundary3D(visualiserRace.getVisualiserRaceState().getRaceDataSource().getBoundary(), gpsConverter);
|
||||
for (Subject3D subject3D: boundary3D.getBoundaryNodes()){
|
||||
@@ -293,8 +434,9 @@ public class RaceViewController extends Controller {
|
||||
}
|
||||
// Position and add each mark to view
|
||||
for(Mark mark: race.getVisualiserRaceState().getMarks()) {
|
||||
MeshView mesh = new MeshView(importerMark.getImport());
|
||||
Subject3D markModel = new Subject3D(mesh, mark.getSourceID());
|
||||
// MeshView mesh = new MeshView(importerMark.getImport());
|
||||
// Subject3D markModel = new Subject3D(mesh, mark.getSourceID());
|
||||
Subject3D markModel = new Subject3D(Assets3D.getMark(), mark.getSourceID());
|
||||
|
||||
markModel.setX(gpsConverter.convertGPS(mark.getPosition()).getX());
|
||||
markModel.setZ(gpsConverter.convertGPS(mark.getPosition()).getY());
|
||||
@@ -302,35 +444,158 @@ public class RaceViewController extends Controller {
|
||||
viewSubjects.add(markModel);
|
||||
}
|
||||
|
||||
for (VisualiserBoat boat: race.getVisualiserRaceState().getBoats()) {
|
||||
// Position and add each boat to view
|
||||
for(VisualiserBoat boat: race.getVisualiserRaceState().getBoats()) {
|
||||
Shape3D mesh = Assets3D.getBoat();
|
||||
|
||||
PhongMaterial boatColorMat = new PhongMaterial(boat.getColor());
|
||||
//mesh.setMaterial(boatColorMat);
|
||||
Subject3D boatModel = new Subject3D(mesh, boat.getSourceID());
|
||||
|
||||
// Configure visualiser for client's boat
|
||||
if (boat.isClientBoat()) {
|
||||
Shockwave boatHighlight = new Shockwave(10);
|
||||
boatHighlight.getMesh().setMaterial(new PhongMaterial(new Color(1, 1, 0, 0.1)));
|
||||
viewSubjects.add(boatHighlight);
|
||||
// Add player boat highlight
|
||||
// Shockwave boatHighlight = new Shockwave(10);
|
||||
// boatHighlight.getMesh().setMaterial(new PhongMaterial(new Color(1, 1, 0, 0.1)));
|
||||
// viewSubjects.add(boatHighlight);
|
||||
|
||||
viewSubjects.add(Assets3D.boatHighlight);
|
||||
|
||||
// Track player boat with camera
|
||||
viewSubjects.add(boatModel);
|
||||
Platform.runLater(() -> {
|
||||
view3D.trackSubject(boatModel, 0);
|
||||
view3D.setThirdPerson();
|
||||
});
|
||||
|
||||
// Track player boat with highlight
|
||||
AnimationTimer highlightTrack = new AnimationTimer() {
|
||||
@Override
|
||||
public void handle(long now) {
|
||||
boatHighlight.setX(gpsConverter.convertGPS(boat.getPosition()).getX());
|
||||
boatHighlight.setZ(gpsConverter.convertGPS(boat.getPosition()).getY());
|
||||
Assets3D.boatHighlight.setX(gpsConverter.convertGPS(boat.getPosition()).getX());
|
||||
Assets3D.boatHighlight.setZ(gpsConverter.convertGPS(boat.getPosition()).getY());
|
||||
}
|
||||
};
|
||||
highlightTrack.start();
|
||||
}
|
||||
}
|
||||
|
||||
// Position and add each boat to view
|
||||
for (VisualiserBoat boat: race.getVisualiserRaceState().getBoats()) {
|
||||
MeshView mesh;
|
||||
if(boat.getSourceID() == race.getVisualiserRaceState().getPlayerBoatID()) {
|
||||
mesh = new MeshView(importer.getImport());
|
||||
// Highlight next mark only for player boat
|
||||
// Material markColor = new PhongMaterial(new Color(0.15,0.9,0.2,1));
|
||||
// CompoundMark nextMark = boat.getCurrentLeg().getEndCompoundMark();
|
||||
// view3D.getShape(nextMark.getMark1().getSourceID()).getMesh().setMaterial(markColor);
|
||||
// if(nextMark.getMark2() != null) {
|
||||
// view3D.getShape(nextMark.getMark2().getSourceID()).getMesh().setMaterial(markColor);
|
||||
// }
|
||||
// boat.legProperty().addListener((o, prev, curr) -> Platform.runLater(() -> swapColours(curr)));
|
||||
|
||||
//next mark indicator
|
||||
changeNextMark(boat.getCurrentLeg());
|
||||
viewSubjects.add(Assets3D.ccwNextArrow);
|
||||
viewSubjects.add(Assets3D.cwNextArrow);
|
||||
boat.legProperty().addListener((o, prev, curr) -> Platform.runLater(() -> changeNextMark(curr)));
|
||||
} else {
|
||||
mesh = new MeshView(importerBurgerBoat.getImport());
|
||||
viewSubjects.add(boatModel);
|
||||
boatModel.getMesh().toFront();
|
||||
}
|
||||
PhongMaterial boatColorMat = new PhongMaterial(boat.getColor());
|
||||
mesh.setMaterial(boatColorMat);
|
||||
Subject3D boatModel = new Subject3D(mesh, boat.getSourceID());
|
||||
|
||||
viewSubjects.add(boatModel);
|
||||
//Create health effect
|
||||
HealthEffect healthEffect = new HealthEffect(boat.getSourceID(), System.currentTimeMillis());
|
||||
viewSubjects.add(healthEffect);
|
||||
healthEffectList.add(healthEffect);
|
||||
|
||||
//add sail
|
||||
Sails3D sails3D = new Sails3D();
|
||||
Subject3D sailsSubject = new Subject3D(sails3D, 0);
|
||||
sails3D.setMouseTransparent(true);
|
||||
sails3D.setMaterial(boatColorMat);
|
||||
sailsSubject.setXRot(0d);
|
||||
sailsSubject.setHeading(visualiserRace.getVisualiserRaceState().getWindDirection().degrees());
|
||||
viewSubjects.add(sailsSubject);
|
||||
|
||||
|
||||
// SeagullFlock seagullFlock = new SeagullFlock(67, 43, 0);
|
||||
//// seagullFlock.addToFlock();
|
||||
//// seagullFlock.addToFlock();
|
||||
// seagullFlock.setxBound(450);
|
||||
// seagullFlock.setyBound(450);
|
||||
// viewSubjects.addAll(seagullFlock.getSeagulls());
|
||||
//
|
||||
// Subject3D textSubject = new Annotation3D(new SeagullMesh());
|
||||
// textSubject.setY(-10);
|
||||
// viewSubjects.add(textSubject);
|
||||
addToFlock();
|
||||
seagullGoTo.start();
|
||||
|
||||
AnimationTimer rotateArrows = new AnimationTimer() {
|
||||
@Override
|
||||
public void handle(long now) {
|
||||
Assets3D.ccwNextArrow.setHeading(Assets3D.ccwNextArrow.getHeading().getAngle() - 3.0);
|
||||
Assets3D.cwNextArrow.setHeading(Assets3D.cwNextArrow.getHeading().getAngle() + 3.0);
|
||||
}
|
||||
};
|
||||
rotateArrows.start();
|
||||
|
||||
AnimationTimer sailsFollowBoat = new AnimationTimer() {
|
||||
double sailCurrent = visualiserRace.getVisualiserRaceState().getWindDirection().degrees();
|
||||
boolean canLuff = true;
|
||||
double turnRate = 5;
|
||||
|
||||
@Override
|
||||
public void handle(long now) {
|
||||
double sailDir;
|
||||
//if sails are out etc
|
||||
if (boat.isSailsOut()) {
|
||||
double windDir = visualiserRace.getVisualiserRaceState().getWindDirection().degrees();
|
||||
double windOffset = (360 - windDir + boat.getBearing().degrees()) % 360;
|
||||
sailDir = windOffset / 180 * 270 + windDir + 180;
|
||||
boolean leftOfWind = windOffset >= 180;
|
||||
if (leftOfWind){
|
||||
sailDir = -sailDir;
|
||||
} else {
|
||||
sailDir = windDir - sailDir;
|
||||
}
|
||||
} else {
|
||||
sailDir = visualiserRace.getVisualiserRaceState().getWindDirection().degrees();
|
||||
}
|
||||
|
||||
//get new place to move towards
|
||||
double compA = ((sailCurrent - sailDir) % 360 + 360) % 360;//degrees right
|
||||
if (compA > 180) compA = 360 - compA;
|
||||
double compB = ((sailDir - sailCurrent) % 360 + 360) % 360;//degrees left
|
||||
if (compB > compA){
|
||||
if (compA > turnRate){
|
||||
sailCurrent = ((sailCurrent - turnRate) % 360 + 360) % 360;
|
||||
canLuff = false;
|
||||
} else {
|
||||
sailCurrent = sailDir;
|
||||
canLuff = true;
|
||||
}
|
||||
} else {
|
||||
if (compB > turnRate){
|
||||
sailCurrent = ((sailCurrent + turnRate) % 360 + 360) % 360;
|
||||
canLuff = false;
|
||||
} else {
|
||||
sailCurrent = sailDir;
|
||||
canLuff = true;
|
||||
}
|
||||
}
|
||||
sailsSubject.setHeading(sailCurrent);
|
||||
if (canLuff) {
|
||||
if (boat.isSailsOut()) {
|
||||
if (sails3D.isLuffing()) {
|
||||
sails3D.stopLuffing();
|
||||
}
|
||||
} else {
|
||||
if (!sails3D.isLuffing()) {
|
||||
sails3D.startLuffing();
|
||||
}
|
||||
}
|
||||
}
|
||||
sailsSubject.setX(gpsConverter.convertGPS(boat.getPosition()).getX());
|
||||
sailsSubject.setZ(gpsConverter.convertGPS(boat.getPosition()).getY());
|
||||
sailsSubject.getMesh().toFront();
|
||||
}
|
||||
};
|
||||
sailsFollowBoat.start();
|
||||
|
||||
// Track this boat's movement with the new subject
|
||||
AnimationTimer trackBoat = new AnimationTimer() {
|
||||
@@ -339,21 +604,31 @@ public class RaceViewController extends Controller {
|
||||
boatModel.setHeading(boat.getBearing().degrees());
|
||||
boatModel.setX(gpsConverter.convertGPS(boat.getPosition()).getX());
|
||||
boatModel.setZ(gpsConverter.convertGPS(boat.getPosition()).getY());
|
||||
boatModel.getMesh().toFront();
|
||||
|
||||
//Fire follows boat
|
||||
healthEffect.setHeading(boat.getBearing().degrees());
|
||||
healthEffect.setX(gpsConverter.convertGPS(boat.getPosition()).getX());
|
||||
healthEffect.setZ(gpsConverter.convertGPS(boat.getPosition()).getY());
|
||||
healthEffect.setY(0);
|
||||
|
||||
}
|
||||
};
|
||||
trackBoat.start();
|
||||
|
||||
Material markColor = new PhongMaterial(new Color(0.15,0.9,0.2,1));
|
||||
CompoundMark nextMark = boat.getCurrentLeg().getEndCompoundMark();
|
||||
//next mark indicator
|
||||
//Material markColor = new PhongMaterial(new Color(0.15,0.9,0.2,1));
|
||||
/*CompoundMark nextMark = boat.getCurrentLeg().getEndCompoundMark();
|
||||
|
||||
view3D.getShape(nextMark.getMark1().getSourceID()).getMesh().setMaterial(markColor);
|
||||
if(nextMark.getMark2() != null) {
|
||||
view3D.getShape(nextMark.getMark2().getSourceID()).getMesh().setMaterial(markColor);
|
||||
}
|
||||
Subject3D shockwave = new Shockwave(10);
|
||||
}*/
|
||||
|
||||
Subject3D shockwave = new Shockwave(10);
|
||||
viewSubjects.add(shockwave);
|
||||
|
||||
boat.legProperty().addListener((o, prev, curr) -> Platform.runLater(() -> swapColours(curr)));
|
||||
//boat.legProperty().addListener((o, prev, curr) -> Platform.runLater(() -> swapColours(curr)));
|
||||
boat.hasCollidedProperty().addListener((o, prev, curr) -> Platform.runLater(() -> showCollision(boat, shockwave)));
|
||||
}
|
||||
// Fix initial bird's-eye position
|
||||
@@ -369,7 +644,12 @@ public class RaceViewController extends Controller {
|
||||
|
||||
// Bind zooming to scrolling
|
||||
view3D.setOnScroll(e -> {
|
||||
view3D.updateDistance(e.getDeltaY());
|
||||
//view3D.updateDistance(e.getDeltaY());
|
||||
if (e.getDeltaY() > 0) {
|
||||
view3D.zoomIn();
|
||||
} else {
|
||||
view3D.zoomOut();
|
||||
}
|
||||
});
|
||||
|
||||
// Bind zooming to keypress (Z/X default)
|
||||
@@ -390,7 +670,7 @@ public class RaceViewController extends Controller {
|
||||
}
|
||||
}
|
||||
}
|
||||
view3D.updateDistance(-10);
|
||||
view3D.zoomIn();
|
||||
break;
|
||||
case "Zoom Out":
|
||||
//Check if race is a tutorial
|
||||
@@ -405,7 +685,7 @@ public class RaceViewController extends Controller {
|
||||
}
|
||||
}
|
||||
}
|
||||
view3D.updateDistance(10);
|
||||
view3D.zoomOut();
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -438,44 +718,99 @@ public class RaceViewController extends Controller {
|
||||
}
|
||||
|
||||
private void addThirdPersonAnnotations(Subject3D subject3D) {
|
||||
viewSubjects.add(nextMarkArrow);
|
||||
final VisualiserBoat boat;
|
||||
try {
|
||||
boat = visualiserRace.getVisualiserRaceState().getBoat(subject3D.getSourceID());
|
||||
} catch (BoatNotFoundException e) {
|
||||
e.printStackTrace();
|
||||
return;
|
||||
}
|
||||
arrowToNextMark = new AnimationTimer() {
|
||||
@Override
|
||||
public void handle(long now) {
|
||||
CompoundMark target = boat.getCurrentLeg().getEndCompoundMark();
|
||||
if (target != null) {
|
||||
Bearing headingToMark = GPSCoordinate.calculateBearing(boat.getPosition(), target.getAverageGPSCoordinate());
|
||||
|
||||
nextMarkArrow.setX(view3D.getPivot().getX());
|
||||
nextMarkArrow.setY(view3D.getPivot().getY());
|
||||
nextMarkArrow.setZ(view3D.getPivot().getZ() + 15);
|
||||
nextMarkArrow.setHeading(headingToMark.degrees());
|
||||
}
|
||||
}
|
||||
};
|
||||
arrowToNextMark.start();
|
||||
nextMarkController.show3d();
|
||||
// viewSubjects.add(nextMarkArrow);
|
||||
// final VisualiserBoat boat;
|
||||
// try {
|
||||
// boat = visualiserRace.getVisualiserRaceState().getBoat(subject3D.getSourceID());
|
||||
// } catch (BoatNotFoundException e) {
|
||||
// e.printStackTrace();
|
||||
// return;
|
||||
// }
|
||||
// arrowToNextMark = new AnimationTimer() {
|
||||
// @Override
|
||||
// public void handle(long now) {
|
||||
// CompoundMark target = boat.getCurrentLeg().getEndCompoundMark();
|
||||
// Bearing headingToMark = GPSCoordinate.calculateBearing(boat.getPosition(), target.getAverageGPSCoordinate());
|
||||
//
|
||||
// nextMarkArrow.setX(view3D.getPivot().getX());
|
||||
// nextMarkArrow.setY(view3D.getPivot().getY());
|
||||
// nextMarkArrow.setZ(view3D.getPivot().getZ() + 15);
|
||||
// nextMarkArrow.setHeading(headingToMark.degrees());
|
||||
// }
|
||||
// };
|
||||
// arrowToNextMark.start();
|
||||
}
|
||||
|
||||
private void removeThirdPersonAnnotations() {
|
||||
viewSubjects.remove(nextMarkArrow);
|
||||
if (arrowToNextMark != null) {
|
||||
arrowToNextMark.stop();
|
||||
}
|
||||
|
||||
nextMarkController.show2d();
|
||||
}
|
||||
|
||||
/**
|
||||
* Swap the colour of the next mark to pass with the last mark passed
|
||||
* @param leg boat has started on
|
||||
*/
|
||||
private void swapColours(Leg leg) {
|
||||
private void setNextMarkArrowDirection(Mark mark1, Mark mark2, RoundingType roundingType, int direction){
|
||||
boolean port = roundingType == RoundingType.Port;
|
||||
Mark chosenMark = null;
|
||||
|
||||
if (mark2 == null){
|
||||
chosenMark = mark1;
|
||||
} else {
|
||||
boolean rounding = port;
|
||||
switch (direction) {
|
||||
case 2:
|
||||
rounding = !port;
|
||||
case 0:
|
||||
if (rounding){
|
||||
if (mark1.getPosition().getLongitude() < mark2.getPosition().getLongitude()){
|
||||
chosenMark = mark1;
|
||||
} else {
|
||||
chosenMark = mark2;
|
||||
}
|
||||
} else {
|
||||
if (mark1.getPosition().getLongitude() > mark2.getPosition().getLongitude()){
|
||||
chosenMark = mark1;
|
||||
} else {
|
||||
chosenMark = mark2;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 3:
|
||||
rounding = !port;
|
||||
case 1:
|
||||
if (rounding){
|
||||
if (mark1.getPosition().getLatitude() > mark2.getPosition().getLatitude()){
|
||||
chosenMark = mark1;
|
||||
} else {
|
||||
chosenMark = mark2;
|
||||
}
|
||||
} else {
|
||||
if (mark1.getPosition().getLatitude() < mark2.getPosition().getLatitude()){
|
||||
chosenMark = mark1;
|
||||
} else {
|
||||
chosenMark = mark2;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (chosenMark == null){
|
||||
System.err.println("Mark to pick is null.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (port){
|
||||
Assets3D.ccwNextArrow.setX(gpsConverter.convertGPS(chosenMark.getPosition()).getX());
|
||||
Assets3D.ccwNextArrow.setZ(gpsConverter.convertGPS(chosenMark.getPosition()).getY());
|
||||
Assets3D.ccwNextArrow.getMesh().setVisible(true);
|
||||
Assets3D.cwNextArrow.getMesh().setVisible(false);
|
||||
} else {
|
||||
Assets3D.cwNextArrow.setX(gpsConverter.convertGPS(chosenMark.getPosition()).getX());
|
||||
Assets3D.cwNextArrow.setZ(gpsConverter.convertGPS(chosenMark.getPosition()).getY());
|
||||
Assets3D.cwNextArrow.getMesh().setVisible(true);
|
||||
Assets3D.ccwNextArrow.getMesh().setVisible(false);
|
||||
}
|
||||
}
|
||||
|
||||
private void changeNextMark(Leg leg){
|
||||
CompoundMark start = leg.getStartCompoundMark();
|
||||
CompoundMark end = leg.getEndCompoundMark();
|
||||
|
||||
@@ -484,23 +819,28 @@ public class RaceViewController extends Controller {
|
||||
return;
|
||||
}
|
||||
|
||||
Shape3D start1 = view3D.getShape(start.getMark1().getSourceID()).getMesh();
|
||||
Shape3D end1 = view3D.getShape(end.getMark1().getSourceID()).getMesh();
|
||||
|
||||
Material nextMark = start1.getMaterial();
|
||||
Material lastMark = end1.getMaterial();
|
||||
|
||||
start1.setMaterial(lastMark);
|
||||
if(start.getMark2() != null) {
|
||||
Shape3D start2 = view3D.getShape(start.getMark2().getSourceID()).getMesh();
|
||||
start2.setMaterial(lastMark);
|
||||
//find direction coming
|
||||
double angle = gpsConverter.getAngle(gpsConverter.convertGPS(start.getMark1().getPosition()),
|
||||
gpsConverter.convertGPS(end.getMark1Position()));
|
||||
angle = (Math.toDegrees(angle) % 360 + 360) % 360;
|
||||
int dir = 0; //0 = top 1 = right 2 = down 3 = down
|
||||
if (angle < 315){
|
||||
if (angle > 45){
|
||||
dir ++;
|
||||
}
|
||||
if (angle > 135){
|
||||
dir ++;
|
||||
}
|
||||
if (angle > 225){
|
||||
dir ++;
|
||||
}
|
||||
}
|
||||
|
||||
end1.setMaterial(nextMark);
|
||||
if(end.getMark2() != null) {
|
||||
Shape3D end2 = view3D.getShape(end.getMark2().getSourceID()).getMesh();
|
||||
end2.setMaterial(nextMark);
|
||||
}
|
||||
Mark startMark1 = end.getMark1();
|
||||
Mark startMark2 = end.getMark2();
|
||||
|
||||
setNextMarkArrowDirection(startMark1, startMark2, end.getRoundingType(), dir);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -544,13 +884,17 @@ public class RaceViewController extends Controller {
|
||||
// set table data
|
||||
boatInfoTable.setItems(sortedBoats);
|
||||
boatTeamColumn.setCellValueFactory(
|
||||
cellData -> cellData.getValue().nameProperty());
|
||||
cellData -> cellData.getValue().nameProperty()
|
||||
);
|
||||
boatSpeedColumn.setCellValueFactory(
|
||||
cellData -> cellData.getValue().currentSpeedProperty());
|
||||
cellData -> cellData.getValue().currentSpeedProperty()
|
||||
);
|
||||
boatMarkColumn.setCellValueFactory(
|
||||
cellData -> cellData.getValue().legProperty());
|
||||
boatPlacingColumn.setCellValueFactory(
|
||||
cellData -> cellData.getValue().placingProperty());
|
||||
cellData -> cellData.getValue().legProperty()
|
||||
);
|
||||
boatHealthColumn.setCellValueFactory(
|
||||
cellData -> cellData.getValue().healthProperty()
|
||||
);
|
||||
|
||||
//Kind of ugly, but allows for formatting an observed speed.
|
||||
boatSpeedColumn.setCellFactory(
|
||||
@@ -650,6 +994,101 @@ public class RaceViewController extends Controller {
|
||||
}.start();
|
||||
}
|
||||
|
||||
/**
|
||||
* Animation timer loop for the speedometer
|
||||
*/
|
||||
private void speedometerLoop(){
|
||||
new AnimationTimer(){
|
||||
@Override
|
||||
public void handle(long arg0){
|
||||
if (raceState.getRaceStatusEnum() == RaceStatusEnum.FINISHED) {
|
||||
stop(); // stop the timer
|
||||
} else {
|
||||
try {
|
||||
//Set the current speed value of the boat
|
||||
gauge.setValue(raceState.getBoat(raceState.getPlayerBoatID()).getCurrentSpeed());
|
||||
fGauge.getGauge().setValue(raceState.getBoat(raceState.getPlayerBoatID()).getCurrentSpeed());
|
||||
|
||||
//Create list with sorted boat placements
|
||||
List<VisualiserBoat> boatList = boatInfoTable.getItems();
|
||||
for (VisualiserBoat boat : boatList){
|
||||
if(raceState.getPlayerBoatID()==boat.getSourceID()){
|
||||
//Set boat current placement value as title of speedometer
|
||||
gauge.titleProperty().setValue("Position: " + (boatInfoTable.getItems().indexOf(boat)+1));
|
||||
fGauge.getGauge().titleProperty().setValue("Position: " + (boatInfoTable.getItems().indexOf(boat)+1));
|
||||
}
|
||||
}
|
||||
} catch (BoatNotFoundException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
}.start();
|
||||
}
|
||||
|
||||
/**
|
||||
* Animation timer for health
|
||||
*/
|
||||
private void healthLoop(){
|
||||
new AnimationTimer(){
|
||||
@Override
|
||||
public void handle(long arg0){
|
||||
if (raceState.getRaceStatusEnum() == RaceStatusEnum.FINISHED) {
|
||||
stop(); // stop the timer
|
||||
} else {
|
||||
try {
|
||||
//Check if boat is dead
|
||||
if(raceState.getBoat(raceState.getPlayerBoatID()).getHealth()<=0){
|
||||
if(!deathPane.isDisable()) {
|
||||
deathPane.setVisible(true);
|
||||
}
|
||||
}
|
||||
} catch (BoatNotFoundException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
for(VisualiserBoat boat : raceState.getBoats()){
|
||||
for (HealthEffect fp : healthEffectList){
|
||||
|
||||
if(fp.getSourceID()==boat.getSourceID()){
|
||||
if(boat.getHealth()<=0){
|
||||
//Boat is dead. Don't check it anymore for hp
|
||||
fp.displayDeath(fp.getSourceID()==raceState.getPlayerBoatID());
|
||||
Annotation3D sharks = new Annotation3D(Assets3D.getSharks());
|
||||
sharks.setX(gpsConverter.convertGPS(boat.getPosition()).getX());
|
||||
sharks.setZ(gpsConverter.convertGPS(boat.getPosition()).getY());
|
||||
viewSubjects.add(sharks);
|
||||
new AnimationTimer(){
|
||||
@Override
|
||||
public void handle(long now) {
|
||||
sharks.setHeading(sharks.getHeading().getAngle() - 0.5);
|
||||
}
|
||||
}.start();
|
||||
|
||||
fp.setSourceID(0);
|
||||
}
|
||||
else
|
||||
//Speed up tick when <=10 hp
|
||||
if(boat.getHealth()<=10){
|
||||
fp.flash(System.currentTimeMillis(), 300, boat.getSourceID()==raceState.getPlayerBoatID());
|
||||
}
|
||||
else
|
||||
//Visual indication of low hp
|
||||
if(boat.getHealth()<=20) {
|
||||
//fp.setVisible(true);
|
||||
fp.flash(System.currentTimeMillis(), 500, boat.getSourceID()==raceState.getPlayerBoatID());
|
||||
} else {
|
||||
fp.setVisible(false);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}.start();
|
||||
}
|
||||
|
||||
/**
|
||||
* toggles if the info table is shown
|
||||
*/
|
||||
@@ -783,25 +1222,115 @@ public class RaceViewController extends Controller {
|
||||
|
||||
private void bigMap(){
|
||||
if (mapToggle){
|
||||
raceCanvas.widthProperty().bind(canvasBase.widthProperty());
|
||||
raceCanvas.heightProperty().bind(canvasBase.heightProperty());
|
||||
raceCanvas.widthProperty().bind(canvasBase2.widthProperty());
|
||||
raceCanvas.heightProperty().bind(canvasBase2.heightProperty());
|
||||
|
||||
raceCanvas.setFullScreen(true);
|
||||
raceCanvas.setOpacity(0.6);
|
||||
|
||||
canvasBase1.getChildren().remove(raceCanvas);
|
||||
canvasBase.getChildren().add(1, raceCanvas);
|
||||
canvasBase2.getChildren().add(0, raceCanvas);
|
||||
|
||||
}else{
|
||||
raceCanvas.widthProperty().bind(canvasBase1.widthProperty());
|
||||
raceCanvas.heightProperty().bind(canvasBase1.heightProperty());
|
||||
|
||||
raceCanvas.setFullScreen(false);
|
||||
raceCanvas.setOpacity(1);
|
||||
|
||||
canvasBase.getChildren().remove(raceCanvas);
|
||||
canvasBase2.getChildren().remove(raceCanvas);
|
||||
canvasBase1.getChildren().add(0, raceCanvas);
|
||||
}
|
||||
mapToggle = !mapToggle;
|
||||
}
|
||||
|
||||
/**
|
||||
* FXML method for death button
|
||||
*/
|
||||
public void deathOKPressed(){
|
||||
deathPane.setDisable(true);
|
||||
deathPane.setVisible(false);
|
||||
}
|
||||
|
||||
|
||||
private AnimationTimer seagullGoTo = new AnimationTimer() {
|
||||
@Override
|
||||
public void handle(long now) {
|
||||
for (Subject3D seagull: seagulls) {
|
||||
if (seagullsGoToX.get(seagull).size() > 0) {
|
||||
//System.out.println(xPosition + " " + yPosition);
|
||||
seagull.setHeading(GPSConverter.getAngle(new GraphCoordinate(seagull.getX(), seagull.getZ()),
|
||||
new GraphCoordinate(seagullsGoToX.get(seagull).get(0), seagullsGoToY.get(seagull).get(0))));
|
||||
double delx = seagullsGoToX.get(seagull).get(0) - seagull.getX();
|
||||
double dely = seagullsGoToY.get(seagull).get(0) - seagull.getZ();
|
||||
double scale = seagullSpeed / Math.sqrt(delx * delx + dely * dely);
|
||||
if (scale < 1) {
|
||||
seagull.setX(seagull.getX() + delx * scale);
|
||||
seagull.setZ(seagull.getZ() + dely * scale);
|
||||
} else {
|
||||
seagullsGoToX.get(seagull).remove(0);
|
||||
seagullsGoToY.get(seagull).remove(0);
|
||||
}
|
||||
} else {
|
||||
randSeagullNewAction(seagull);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
public void randSeagullNewAction(Subject3D seagull){
|
||||
Random rand = new Random();
|
||||
int nextAction = rand.nextInt(1);
|
||||
switch(nextAction){
|
||||
case 0:
|
||||
//do a straight line
|
||||
double nextX = rand.nextInt((int)gpsConverter.getLongitudeFactor());
|
||||
/*if (nextX > this.xBound/2){
|
||||
nextX = - nextX/2;
|
||||
}*/
|
||||
double nextY = rand.nextInt((int)gpsConverter.getLatitudeFactor());
|
||||
/*if (nextY > this.yBound/2){
|
||||
nextY = - nextY/2;
|
||||
}*/
|
||||
seagullsGoToX.get(seagull).add(nextX);
|
||||
seagullsGoToY.get(seagull).add(nextY);
|
||||
break;
|
||||
case 1:
|
||||
//do a octogan circle
|
||||
seagullsGoToX.get(seagull).add(seagull.getX() - 3);
|
||||
seagullsGoToX.get(seagull).add(seagull.getX() - 3);
|
||||
seagullsGoToX.get(seagull).add(seagull.getX());
|
||||
seagullsGoToX.get(seagull).add(seagull.getX() + 3);
|
||||
seagullsGoToX.get(seagull).add(seagull.getX() + 6);
|
||||
seagullsGoToX.get(seagull).add(seagull.getX() + 6);
|
||||
seagullsGoToX.get(seagull).add(seagull.getX() + 3);
|
||||
seagullsGoToX.get(seagull).add(seagull.getX());
|
||||
//y
|
||||
seagullsGoToY.get(seagull).add(seagull.getZ() - 3);
|
||||
seagullsGoToY.get(seagull).add(seagull.getZ() - 6);
|
||||
seagullsGoToY.get(seagull).add(seagull.getZ() - 9);
|
||||
seagullsGoToY.get(seagull).add(seagull.getZ() - 9);
|
||||
seagullsGoToY.get(seagull).add(seagull.getZ() - 6);
|
||||
seagullsGoToY.get(seagull).add(seagull.getZ() - 3);
|
||||
seagullsGoToY.get(seagull).add(seagull.getZ());
|
||||
seagullsGoToY.get(seagull).add(seagull.getZ());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void addToFlock(){
|
||||
Annotation3D newSeagull = new Annotation3D(new SeagullMesh());
|
||||
newSeagull.setY(-15);
|
||||
newSeagull.setX(67);
|
||||
newSeagull.setZ(43);
|
||||
newSeagull.setXRot(0);
|
||||
seagulls.add(newSeagull);
|
||||
seagullsGoToX.put(newSeagull, new ArrayList<>());
|
||||
seagullsGoToY.put(newSeagull, new ArrayList<>());
|
||||
viewSubjects.add(newSeagull);
|
||||
randSeagullNewAction(newSeagull);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package visualiser.Controllers;
|
||||
|
||||
import com.interactivemesh.jfx.importer.stl.StlMeshImporter;
|
||||
import javafx.animation.AnimationTimer;
|
||||
import javafx.application.Platform;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.fxml.FXML;
|
||||
@@ -9,8 +10,10 @@ import javafx.scene.AmbientLight;
|
||||
import javafx.scene.PointLight;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.RadioButton;
|
||||
import javafx.scene.control.ToggleGroup;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.scene.layout.AnchorPane;
|
||||
import javafx.scene.layout.GridPane;
|
||||
import javafx.scene.layout.Pane;
|
||||
import javafx.scene.media.AudioClip;
|
||||
@@ -41,14 +44,18 @@ import java.util.logging.Logger;
|
||||
* the game.
|
||||
*/
|
||||
public class TitleController extends Controller {
|
||||
private @FXML AnchorPane titleWrapper;
|
||||
private @FXML RadioButton dayModeRD;
|
||||
private @FXML RadioButton nightModeRD;
|
||||
private @FXML Button tutorialButton;
|
||||
private @FXML Pane menuPane;
|
||||
private @FXML ImageView imgSun;
|
||||
private @FXML GridPane view3DContainer;
|
||||
private ToggleGroup toggleGroup = new ToggleGroup();
|
||||
|
||||
public void initialize() {
|
||||
dayModeRD.setToggleGroup(toggleGroup);
|
||||
nightModeRD.setToggleGroup(toggleGroup);
|
||||
|
||||
AmbientLight ambientLight = new AmbientLight(Color.web("#CCCCFF"));
|
||||
ambientLight.setTranslateX(250);
|
||||
ambientLight.setTranslateZ(210);
|
||||
@@ -102,6 +109,8 @@ public class TitleController extends Controller {
|
||||
}
|
||||
};
|
||||
loop.start();
|
||||
|
||||
titleWrapper.getStylesheets().add("/css/dayMode.css");
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -128,10 +137,7 @@ public class TitleController extends Controller {
|
||||
*/
|
||||
public void setDayMode(){
|
||||
dayModeRD.getScene().getStylesheets().clear();
|
||||
menuPane.getStylesheets().clear();
|
||||
imgSun.setImage(new Image(getClass().getResource("/visualiser/images/sun.png").toExternalForm()));
|
||||
dayModeRD.getScene().getStylesheets().add("/css/dayMode.css");
|
||||
menuPane.setStyle("-fx-background-color: #6be6ff;");
|
||||
nightModeRD.setSelected(false);
|
||||
App.dayMode = true;
|
||||
}
|
||||
@@ -141,10 +147,7 @@ public class TitleController extends Controller {
|
||||
*/
|
||||
public void setNightMode(){
|
||||
nightModeRD.getScene().getStylesheets().clear();
|
||||
menuPane.getStylesheets().clear();
|
||||
//imgSun.setImage(new Image(getClass().getResource("/visualiser/images/sunsleep.png").toExternalForm()));
|
||||
nightModeRD.getScene().getStylesheets().add("/css/nightMode.css");
|
||||
menuPane.setStyle("-fx-background-color: #1f2c60;");
|
||||
dayModeRD.setSelected(false);
|
||||
App.dayMode = false;
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@ import javafx.stage.Stage;
|
||||
import javafx.stage.StageStyle;
|
||||
import javafx.util.Duration;
|
||||
import mock.app.Event;
|
||||
import visualiser.layout.Assets3D;
|
||||
|
||||
public class App extends Application {
|
||||
private static Stage stage;
|
||||
@@ -113,6 +114,7 @@ public class App extends Application {
|
||||
}
|
||||
);
|
||||
new Thread(boatTask).start();
|
||||
Assets3D.loadAssets();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,173 @@
|
||||
package visualiser.layout;
|
||||
|
||||
import com.interactivemesh.jfx.importer.obj.ObjModelImporter;
|
||||
import com.interactivemesh.jfx.importer.stl.StlMeshImporter;
|
||||
import com.interactivemesh.jfx.importer.x3d.X3dModelImporter;
|
||||
import javafx.geometry.Point3D;
|
||||
import javafx.scene.Group;
|
||||
import javafx.scene.paint.Color;
|
||||
import javafx.scene.paint.Material;
|
||||
import javafx.scene.paint.PhongMaterial;
|
||||
import javafx.scene.shape.MeshView;
|
||||
import javafx.scene.shape.Shape3D;
|
||||
import visualiser.Controllers.HostGameController;
|
||||
|
||||
import java.net.URL;
|
||||
|
||||
/**
|
||||
* Created by Gondr on 21/09/2017.
|
||||
*/
|
||||
public class Assets3D {
|
||||
|
||||
public static MeshView[] sails;
|
||||
public static MeshView[] sea;
|
||||
public static MeshView[] seagull;
|
||||
public static Subject3D windArrow;
|
||||
public static Subject3D compass;
|
||||
public static Subject3D cwNextArrow;
|
||||
public static Subject3D ccwNextArrow;
|
||||
public static SkyBox skyBox;
|
||||
public static Subject3D boatHighlight;
|
||||
public static Subject3D sharks;
|
||||
|
||||
public static void loadAssets(){
|
||||
loadSails();
|
||||
loadWindArrow();
|
||||
loadNextArrow();
|
||||
loadSeaSurface();
|
||||
loadSkybox();
|
||||
loadSeagull();
|
||||
loadBoatHightlight();
|
||||
}
|
||||
|
||||
private static void loadNextArrow(){
|
||||
StlMeshImporter objModelImporter = new StlMeshImporter();
|
||||
String cwPath = "assets/Next Mark Arrow CW V1.0.stl";
|
||||
URL asset = Assets3D.class.getClassLoader().getResource(cwPath);
|
||||
objModelImporter.read(asset);
|
||||
Material markColor = new PhongMaterial(new Color(0.15,0.9,0.2,1));
|
||||
|
||||
MeshView cwMesh = new MeshView(objModelImporter.getImport());
|
||||
cwMesh.setMaterial(markColor);
|
||||
cwMesh.setMouseTransparent(true);
|
||||
cwMesh.toBack();
|
||||
cwMesh.setVisible(false);
|
||||
cwNextArrow = new Annotation3D(cwMesh);
|
||||
|
||||
String ccwPath = "assets/Next Mark Arrow CCW V1.0.stl";
|
||||
URL ccwAsset = Assets3D.class.getClassLoader().getResource(ccwPath);
|
||||
objModelImporter.read(ccwAsset);
|
||||
|
||||
MeshView ccwMesh = new MeshView(objModelImporter.getImport());
|
||||
ccwMesh.setMaterial(markColor);
|
||||
ccwMesh.setMouseTransparent(true);
|
||||
ccwMesh.toBack();
|
||||
cwMesh.setVisible(false);
|
||||
ccwNextArrow = new Annotation3D(ccwMesh);
|
||||
|
||||
}
|
||||
|
||||
private static void loadBoatHightlight(){
|
||||
Material markColor = new PhongMaterial(new Color(0,1,0,0.5));
|
||||
StlMeshImporter objModelImporter = new StlMeshImporter();
|
||||
String path = "assets/V1.0 Boat Highlight.stl";
|
||||
URL highlight = Assets3D.class.getClassLoader().getResource(path);
|
||||
objModelImporter.read(highlight);
|
||||
|
||||
MeshView hMesh = new MeshView(objModelImporter.getImport());
|
||||
hMesh.setMaterial(markColor);
|
||||
hMesh.setMouseTransparent(true);
|
||||
hMesh.toBack();
|
||||
boatHighlight = new Subject3D(hMesh, 0);
|
||||
|
||||
}
|
||||
|
||||
private static void loadSails(){
|
||||
sails = new MeshView[40];
|
||||
ObjModelImporter objModelImporter = new ObjModelImporter();
|
||||
|
||||
for (int i = 0; i < sails.length; i++){
|
||||
String path = String.format("assets/Sails/V1.5 Sail_%06d.obj", i + 1);
|
||||
URL asset = Assets3D.class.getClassLoader().getResource(path);
|
||||
objModelImporter.read(asset);
|
||||
if (objModelImporter.getImport().length > 0) {
|
||||
sails[i] = objModelImporter.getImport()[0];
|
||||
/*sails[i].setRotationAxis(new Point3D(1, 0, 0));
|
||||
sails[i].setRotate(-90);*/
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void loadSeaSurface(){
|
||||
sea = new MeshView[100];
|
||||
ObjModelImporter objModelImporter = new ObjModelImporter();
|
||||
|
||||
for (int i = 0; i < sea.length; i++){
|
||||
// String path = String.format("assets/Ocean V1.0 Small Animation/Ocean V1.0 Large Animation_%06d.obj", i + 1);
|
||||
// String path = String.format("assets/Ocean V1.0 Animation/Ocean V1.0_%06d.obj", i + 1);
|
||||
String path = String.format("assets/Ocean Animation/Ocean Animation_%06d.obj", i + 1);
|
||||
URL asset = Assets3D.class.getClassLoader().getResource(path);
|
||||
objModelImporter.read(asset);
|
||||
if (objModelImporter.getImport().length > 0) {
|
||||
sea[i] = objModelImporter.getImport()[0];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void loadSeagull(){
|
||||
seagull = new MeshView[30];
|
||||
ObjModelImporter objModelImporter = new ObjModelImporter();
|
||||
|
||||
for (int i = 0; i < seagull.length; i++){
|
||||
String path = String.format("assets/V1.1 Animated/Seagull V1.1_%06d.obj", i + 1);
|
||||
URL asset = Assets3D.class.getClassLoader().getResource(path);
|
||||
objModelImporter.read(asset);
|
||||
if (objModelImporter.getImport().length > 0) {
|
||||
seagull[i] = objModelImporter.getImport()[0];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static Shape3D getBoat(){
|
||||
String path = "assets/V1.4 Boat.x3d";
|
||||
return loadX3d(path);
|
||||
}
|
||||
|
||||
public static Shape3D getMark(){
|
||||
String path = "assets/Burger Bouy V1.1.x3d";
|
||||
return loadX3d(path);
|
||||
}
|
||||
|
||||
private static void loadWindArrow(){
|
||||
String compassPath = "assets/wind_compass.x3d";
|
||||
compass = new Annotation3D(loadX3d(compassPath));
|
||||
String arrowPath = "assets/wind_arrow.x3d";
|
||||
windArrow = new Annotation3D(loadX3d(arrowPath));
|
||||
}
|
||||
|
||||
public static Shape3D getSharks(){
|
||||
String path = "assets/V1.0 Sharks.x3d";
|
||||
return loadX3d(path);
|
||||
}
|
||||
|
||||
public static Shape3D loadX3d(String path){
|
||||
X3dModelImporter x3dModelImporter = new X3dModelImporter();
|
||||
URL asset = Assets3D.class.getClassLoader().getResource(path);
|
||||
x3dModelImporter.read(asset);
|
||||
if (x3dModelImporter.getImport().length > 0) {
|
||||
//if (x3dModelImporter.getImport()[0] instanceof MeshView) {
|
||||
Group g = (Group)((Group)((Group) x3dModelImporter.getImport()[0]).getChildren().get(0)).getChildren().get(0);
|
||||
Shape3D shape = (Shape3D)g.getChildren().get(0);
|
||||
return shape;
|
||||
/*} else {
|
||||
System.err.println("Boat is not a Mesh View 0.0");
|
||||
}*/
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static void loadSkybox(){
|
||||
skyBox = new SkyBox(750, 200, 250, 0, 210);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
package visualiser.layout;
|
||||
|
||||
import javafx.geometry.Point3D;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.media.AudioClip;
|
||||
import javafx.scene.paint.Color;
|
||||
import javafx.scene.paint.PhongMaterial;
|
||||
import javafx.scene.shape.*;
|
||||
import javafx.scene.transform.Rotate;
|
||||
|
||||
/**
|
||||
* Created by zwu18 on 24/09/17.
|
||||
*/
|
||||
public class HealthEffect extends Subject3D {
|
||||
|
||||
private int sourceID;
|
||||
private long currentTime;
|
||||
private AudioClip warningSound = new AudioClip(this.getClass().getResource("/visualiser/sounds/warning.mp3").toExternalForm());
|
||||
private AudioClip deadSound = new AudioClip(this.getClass().getResource("/visualiser/sounds/dead1.wav").toExternalForm());
|
||||
|
||||
public HealthEffect(int sourceID, long currentTime){
|
||||
super(createEffect(), 0);
|
||||
this.sourceID = sourceID;
|
||||
this.currentTime = currentTime;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialise the mesh view with image
|
||||
* @return Mesh view
|
||||
*/
|
||||
private static Shape3D createEffect(){
|
||||
|
||||
Image image = new Image(HealthEffect.class.getClassLoader().getResourceAsStream("images/warning.png"));
|
||||
|
||||
Plane3D plane = new Plane3D(20, 20, 10, 10);
|
||||
|
||||
PhongMaterial material = new PhongMaterial();
|
||||
material.setDiffuseColor(Color.web("#FFFFFF"));
|
||||
material.setSpecularColor(Color.web("#000000"));
|
||||
material.setDiffuseMap(image);
|
||||
|
||||
MeshView imageSurface = new MeshView(plane);
|
||||
|
||||
|
||||
imageSurface.setMaterial(material);
|
||||
imageSurface.setMouseTransparent(true);
|
||||
//imageSurface.toFront(); this.flashInterval = flashInterval;
|
||||
|
||||
return imageSurface;
|
||||
}
|
||||
|
||||
public void rotateView(Double angle, Double pivotX, Double pivotY, Double pivotZ, Point3D axis){
|
||||
Rotate rotate = new Rotate(angle, axis);
|
||||
rotate.setPivotX(pivotX);
|
||||
rotate.setPivotY(pivotY);
|
||||
rotate.setPivotZ(pivotZ);
|
||||
this.getMesh().getTransforms().add(rotate);
|
||||
}
|
||||
|
||||
public void setVisible(boolean bool){
|
||||
this.getMesh().setVisible(bool);
|
||||
}
|
||||
|
||||
|
||||
public int getSourceID(){
|
||||
return sourceID;
|
||||
}
|
||||
|
||||
public void setSourceID(int id){
|
||||
this.sourceID = id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Display visual indication when boat dies
|
||||
* @param player boolean if player is current user or not
|
||||
*/
|
||||
public void displayDeath(boolean player){
|
||||
Image image = new Image(HealthEffect.class.getClassLoader().getResourceAsStream("images/warning2.png"));
|
||||
PhongMaterial material = (PhongMaterial) this.getMesh().getMaterial();
|
||||
material.setDiffuseColor(Color.web("#FFFFFF"));
|
||||
material.setSpecularColor(Color.web("#000000"));
|
||||
material.setDiffuseMap(image);
|
||||
this.getMesh().setMaterial(material);
|
||||
if(player) {
|
||||
deadSound.play();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Flash the mesh view at a certain interval
|
||||
* @param checkTime The current time of flash
|
||||
* @param flashInterval Desired flash interval
|
||||
* @param playerBoat Whether or not this effect is for the player's boat.
|
||||
*/
|
||||
public void flash(long checkTime, long flashInterval, boolean playerBoat){
|
||||
if(checkTime >= (currentTime+flashInterval)){
|
||||
this.currentTime = checkTime;
|
||||
if(this.getMesh().isVisible()){
|
||||
this.setVisible(false);
|
||||
} else {
|
||||
if(playerBoat) {
|
||||
warningSound.setVolume(0.1);
|
||||
warningSound.play();
|
||||
}
|
||||
this.setVisible(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
package visualiser.layout;
|
||||
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.image.WritableImage;
|
||||
import javafx.scene.layout.*;
|
||||
import javafx.scene.shape.Rectangle;
|
||||
|
||||
import java.awt.image.BufferedImage;
|
||||
|
||||
/**
|
||||
* Created by connortaylorbrown on 21/09/17.
|
||||
*/
|
||||
public class HealthSlider extends Pane {
|
||||
/**
|
||||
* Image used to fill health slider
|
||||
*/
|
||||
private Image fillImage;
|
||||
/**
|
||||
* Size of background for image configuration
|
||||
*/
|
||||
private BackgroundSize backgroundSize;
|
||||
/**
|
||||
* Percentage of image cropped out from top
|
||||
*/
|
||||
private double crop;
|
||||
|
||||
public HealthSlider(Image fillImage) {
|
||||
this.fillImage = fillImage;
|
||||
this.crop = 1;
|
||||
this.backgroundSize = new BackgroundSize(
|
||||
100,
|
||||
100,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
false);
|
||||
drawSlider();
|
||||
}
|
||||
|
||||
public void setCrop(double crop) {
|
||||
this.crop = crop;
|
||||
drawSlider();
|
||||
}
|
||||
|
||||
private void drawSlider() {
|
||||
int top = Math.max(0,(int)(fillImage.getHeight() - crop * fillImage.getHeight()));
|
||||
|
||||
WritableImage croppedImage = new WritableImage(
|
||||
fillImage.getPixelReader(),
|
||||
0,
|
||||
top,
|
||||
(int)fillImage.getWidth(),
|
||||
(int)fillImage.getHeight() - top
|
||||
);
|
||||
|
||||
BackgroundImage backgroundImage = new BackgroundImage(
|
||||
croppedImage,
|
||||
BackgroundRepeat.NO_REPEAT,
|
||||
BackgroundRepeat.NO_REPEAT,
|
||||
BackgroundPosition.CENTER,
|
||||
backgroundSize
|
||||
);
|
||||
|
||||
this.setBackground(new Background(backgroundImage));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package visualiser.layout;
|
||||
|
||||
import javafx.animation.AnimationTimer;
|
||||
import javafx.scene.paint.Color;
|
||||
import javafx.scene.paint.Material;
|
||||
import javafx.scene.paint.PhongMaterial;
|
||||
import javafx.scene.shape.MeshView;
|
||||
|
||||
/**
|
||||
* Created by Gondr on 27/09/2017.
|
||||
*/
|
||||
public class NewSeaSurface extends MeshView{
|
||||
private int seaIndex = 0;
|
||||
|
||||
private AnimationTimer seaRipple = new AnimationTimer() {
|
||||
@Override
|
||||
public void handle(long now) {
|
||||
seaIndex = (seaIndex + 1) % Assets3D.sea.length;
|
||||
setMesh(Assets3D.sea[seaIndex].getMesh());
|
||||
}
|
||||
};
|
||||
|
||||
public NewSeaSurface(){
|
||||
super(Assets3D.sea[0].getMesh());
|
||||
Color seaBlue = new Color(0.284, 0.573, 1.00, 1);
|
||||
Material seaMat = new PhongMaterial(seaBlue);
|
||||
setMaterial(seaMat);
|
||||
setMouseTransparent(true);
|
||||
seaRipple.start();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package visualiser.layout;
|
||||
|
||||
import javafx.animation.AnimationTimer;
|
||||
import javafx.scene.shape.Mesh;
|
||||
import javafx.scene.shape.MeshView;
|
||||
|
||||
/**
|
||||
* Created by Gondr on 21/09/2017.
|
||||
*/
|
||||
public class Sails3D extends MeshView {
|
||||
private int index = 0;
|
||||
private boolean isLuffing = false;
|
||||
|
||||
public Sails3D() {
|
||||
setMesh(Assets3D.sails[0].getMesh());
|
||||
}
|
||||
|
||||
private AnimationTimer luff = new AnimationTimer() {
|
||||
@Override
|
||||
public void handle(long now) {
|
||||
setMesh(Assets3D.sails[index].getMesh());
|
||||
index = (index + 1) % 40;
|
||||
}
|
||||
};
|
||||
|
||||
public void startLuffing(){
|
||||
luff.start();
|
||||
isLuffing = true;
|
||||
}
|
||||
|
||||
public void stopLuffing(){
|
||||
luff.stop();
|
||||
isLuffing = false;
|
||||
setMesh(Assets3D.sails[0].getMesh());
|
||||
}
|
||||
|
||||
public boolean isLuffing() {
|
||||
return isLuffing;
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
package visualiser.layout;
|
||||
|
||||
import javafx.animation.AnimationTimer;
|
||||
import javafx.geometry.Point3D;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.image.ImageView;
|
||||
@@ -16,7 +17,7 @@ import visualiser.utils.PerlinNoiseGenerator;
|
||||
/**
|
||||
* Creates a SeaSurface
|
||||
*/
|
||||
public class SeaSurface extends Subject3D {
|
||||
public class SeaSurface extends Annotation3D {
|
||||
private static Image image;
|
||||
|
||||
/**
|
||||
@@ -26,7 +27,7 @@ public class SeaSurface extends Subject3D {
|
||||
* @param freq frequency the perlin noise is to be generated at
|
||||
*/
|
||||
public SeaSurface(int size, double freq) {
|
||||
super(createSurface(size, freq), 0);
|
||||
super(createSurface(size, freq));
|
||||
image = new Image(getClass().getClassLoader().getResourceAsStream("images/skybox/ThickCloudsWaterDown2048.png"));
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,144 @@
|
||||
package visualiser.layout;
|
||||
|
||||
import javafx.animation.AnimationTimer;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.scene.transform.Translate;
|
||||
import visualiser.model.GraphCoordinate;
|
||||
import visualiser.utils.GPSConverter;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
|
||||
/**
|
||||
* Created by Gondr on 28/09/2017.
|
||||
*/
|
||||
public class SeagullFlock{
|
||||
private ObservableList<Subject3D> seagulls = FXCollections.observableArrayList();
|
||||
private double xPos = 0;
|
||||
private double yPos = 0;
|
||||
private double zPos = 20;
|
||||
private double heading = 0;
|
||||
private double[] xOffset = {0, -5, -5};
|
||||
private double[] yOffset = {0, -5, 5};
|
||||
private double xBound = Integer.MAX_VALUE;//-/+
|
||||
private double yBound = Integer.MAX_VALUE;
|
||||
private List<Double> goToX = new ArrayList<>();
|
||||
private List<Double> goToY = new ArrayList<>();
|
||||
|
||||
private double speed = 0.01;
|
||||
|
||||
private AnimationTimer goTo = new AnimationTimer() {
|
||||
@Override
|
||||
public void handle(long now) {
|
||||
if (goToX.size() > 0) {
|
||||
//System.out.println(xPosition + " " + yPosition);
|
||||
heading = GPSConverter.getAngle(new GraphCoordinate(xPos, yPos),
|
||||
new GraphCoordinate(goToX.get(0), goToY.get(0)));
|
||||
double delx = goToX.get(0) - xPos;
|
||||
double dely = goToY.get(0) - yPos;
|
||||
double scale = speed / Math.sqrt(delx * delx + dely * dely);
|
||||
if (scale < 1) {
|
||||
xPos += delx * scale;
|
||||
yPos += dely * scale;
|
||||
} else {
|
||||
goToX.remove(0);
|
||||
goToY.remove(0);
|
||||
}
|
||||
}else {
|
||||
stop();
|
||||
randNewAction();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private AnimationTimer update = new AnimationTimer() {
|
||||
@Override
|
||||
public void handle(long now) {
|
||||
for (int i = 0; i < seagulls.size(); i++){
|
||||
Subject3D seagull = seagulls.get(i);
|
||||
seagull.getMesh().setTranslateY(zPos);
|
||||
|
||||
seagull.setHeading(heading);
|
||||
seagull.setX(xPos + xOffset[i]);
|
||||
seagull.setZ(yPos + yOffset[i]);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
public SeagullFlock() {
|
||||
this(0d, 0d, 0d);
|
||||
}
|
||||
|
||||
public SeagullFlock(double x, double y, double z){
|
||||
xPos = x;
|
||||
yPos = y;
|
||||
zPos = z;
|
||||
addToFlock();
|
||||
update.start();
|
||||
randNewAction();
|
||||
}
|
||||
|
||||
public void addToFlock(){
|
||||
if (seagulls.size() < 3) {
|
||||
Annotation3D newSeagull = new Annotation3D(new SeagullMesh());
|
||||
newSeagull.setXRot(0);
|
||||
seagulls.add(newSeagull);
|
||||
// seagulls.add(new Annotation3D(new Sphere(5)));
|
||||
}
|
||||
}
|
||||
|
||||
public void setxBound(double xBound) {
|
||||
this.xBound = xBound;
|
||||
}
|
||||
|
||||
public void setyBound(double yBound) {
|
||||
this.yBound = yBound;
|
||||
}
|
||||
|
||||
public void randNewAction(){
|
||||
Random rand = new Random();
|
||||
int nextAction = rand.nextInt(1);
|
||||
switch(nextAction){
|
||||
case 0:
|
||||
//do a straight line
|
||||
double nextX = rand.nextInt((int)this.xBound);
|
||||
/*if (nextX > this.xBound/2){
|
||||
nextX = - nextX/2;
|
||||
}*/
|
||||
double nextY = rand.nextInt((int)this.yBound);
|
||||
/*if (nextY > this.yBound/2){
|
||||
nextY = - nextY/2;
|
||||
}*/
|
||||
goToX.add(nextX);
|
||||
goToY.add(nextY);
|
||||
break;
|
||||
case 1:
|
||||
//do a octogan circle
|
||||
goToX.add(xPos - 3);
|
||||
goToX.add(xPos - 3);
|
||||
goToX.add(xPos);
|
||||
goToX.add(xPos + 3);
|
||||
goToX.add(xPos + 6);
|
||||
goToX.add(xPos + 6);
|
||||
goToX.add(xPos + 3);
|
||||
goToX.add(xPos);
|
||||
//y
|
||||
goToX.add(yPos - 3);
|
||||
goToX.add(yPos - 6);
|
||||
goToX.add(yPos - 9);
|
||||
goToX.add(yPos - 9);
|
||||
goToX.add(yPos - 6);
|
||||
goToX.add(yPos - 3);
|
||||
goToX.add(yPos);
|
||||
goToX.add(yPos);
|
||||
break;
|
||||
}
|
||||
goTo.start();
|
||||
}
|
||||
|
||||
public List<Subject3D> getSeagulls() {
|
||||
return seagulls;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
package visualiser.layout;
|
||||
|
||||
import javafx.animation.AnimationTimer;
|
||||
import javafx.scene.paint.Color;
|
||||
import javafx.scene.paint.PhongMaterial;
|
||||
import javafx.scene.shape.MeshView;
|
||||
|
||||
import java.util.Random;
|
||||
|
||||
/**
|
||||
* Created by Gondr on 28/09/2017.
|
||||
*/
|
||||
public class SeagullMesh extends MeshView{
|
||||
private int index = 0;
|
||||
private int isFlapping = 0;
|
||||
private int flapPeriod;
|
||||
private int flapStrength;
|
||||
private int periodElapsed = 0;
|
||||
|
||||
public SeagullMesh() {
|
||||
setMesh(Assets3D.seagull[0].getMesh());
|
||||
PhongMaterial white = new PhongMaterial(Color.WHITE);
|
||||
setMaterial(white);
|
||||
Random rand = new Random();
|
||||
flapPeriod = rand.nextInt(9);
|
||||
flapPeriod = 60 * (11 + flapPeriod);
|
||||
flapStrength = rand.nextInt(3) + 2;
|
||||
scheduledFlap.start();
|
||||
}
|
||||
|
||||
private AnimationTimer flap = new AnimationTimer() {
|
||||
@Override
|
||||
public void handle(long now) {
|
||||
index = (index + 1) % Assets3D.seagull.length;
|
||||
setMesh(Assets3D.seagull[index].getMesh());
|
||||
if (index == 0){
|
||||
isFlapping ++;
|
||||
if (isFlapping >= flapStrength){
|
||||
stop();
|
||||
setMesh(Assets3D.seagull[0].getMesh());
|
||||
isFlapping = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private AnimationTimer scheduledFlap = new AnimationTimer() {
|
||||
@Override
|
||||
public void handle(long now) {
|
||||
if (periodElapsed == 0 && isFlapping == 0){
|
||||
startFlapping();
|
||||
}
|
||||
periodElapsed = (periodElapsed + 1) % flapPeriod;
|
||||
}
|
||||
};
|
||||
|
||||
public void startFlapping(){
|
||||
flap.start();
|
||||
}
|
||||
}
|
||||
@@ -28,8 +28,13 @@ public class Subject3D {
|
||||
*/
|
||||
private Rotate heading;
|
||||
|
||||
private Rotate xRot;
|
||||
|
||||
private Rotate yRot;
|
||||
|
||||
private Scale scale;
|
||||
|
||||
|
||||
/**
|
||||
* Constructor for view subject wrapper
|
||||
* @param mesh to be rendered
|
||||
@@ -41,7 +46,9 @@ public class Subject3D {
|
||||
this.scale = new Scale();
|
||||
this.position = new Translate();
|
||||
this.heading = new Rotate(0, Rotate.Y_AXIS);
|
||||
this.mesh.getTransforms().addAll(position, scale, heading, new Rotate(90, Rotate.X_AXIS), new Rotate(180, Rotate.Y_AXIS));
|
||||
this.xRot = new Rotate(90, Rotate.X_AXIS);
|
||||
this.yRot = new Rotate(180, Rotate.Y_AXIS);
|
||||
this.mesh.getTransforms().addAll(position, scale, heading, xRot, yRot);
|
||||
}
|
||||
|
||||
public Shape3D getMesh() {
|
||||
@@ -70,15 +77,35 @@ public class Subject3D {
|
||||
position.setX(x);
|
||||
}
|
||||
|
||||
public double getX(){
|
||||
return position.getX();
|
||||
}
|
||||
|
||||
public void setY(double y) {
|
||||
position.setY(y);
|
||||
}
|
||||
|
||||
public double getY(){
|
||||
return position.getY();
|
||||
}
|
||||
|
||||
public void setZ(double z) {
|
||||
position.setZ(z);
|
||||
}
|
||||
|
||||
public double getZ(){
|
||||
return position.getZ();
|
||||
}
|
||||
|
||||
public void setHeading(double angle) {
|
||||
heading.setAngle(angle);
|
||||
}
|
||||
|
||||
public void setXRot(double angle){
|
||||
xRot.setAngle(angle);
|
||||
}
|
||||
|
||||
public void setYRot(double angle){
|
||||
yRot.setAngle(angle);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ package visualiser.layout;
|
||||
import javafx.animation.AnimationTimer;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.beans.value.ChangeListener;
|
||||
import javafx.collections.ListChangeListener;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.scene.*;
|
||||
@@ -78,11 +77,17 @@ public class View3D extends Pane {
|
||||
/**
|
||||
* Distance to switch from third person to bird's eye
|
||||
*/
|
||||
private final double THIRD_PERSON_LIMIT = 100;
|
||||
private final double THIRD_PERSON_LIMIT = 500;
|
||||
/**
|
||||
* Distance to stop zoom
|
||||
*/
|
||||
private final double FIRST_PERSON_LIMIT = 2;
|
||||
private final double ZOOM_IN_LIMIT = 15;
|
||||
private final double ZOOM_OUT_LIMIT = 700;
|
||||
private final double MAX_ZOOM_LIMIT = 1150;
|
||||
private final double MAX_PITCH = 60; // birds eye view
|
||||
private final double MIN_PITCH = 5; // third person view
|
||||
private final double ZOOM_PER_KEYPRESS = 5; // distance changed per zoom
|
||||
private double itemScale = 1;
|
||||
|
||||
/**
|
||||
* Default constructor for View3D. Sets up Scene and PerspectiveCamera.
|
||||
@@ -183,12 +188,9 @@ public class View3D extends Pane {
|
||||
* Configures camera to third person view
|
||||
*/
|
||||
public void setThirdPerson() {
|
||||
this.setDistance(THIRD_PERSON_LIMIT / 2);
|
||||
this.setPitch(10);
|
||||
|
||||
for(Subject3D item: items) {
|
||||
item.setScale(0.1);
|
||||
}
|
||||
this.setDistance(ZOOM_IN_LIMIT * 5);
|
||||
adjustPitchForZoom();
|
||||
adjustScaleForZoom();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -196,17 +198,14 @@ public class View3D extends Pane {
|
||||
*/
|
||||
public void setBirdsEye() {
|
||||
this.setYaw(0);
|
||||
this.setPitch(60);
|
||||
|
||||
for(Subject3D item: items) {
|
||||
item.setScale(1);
|
||||
}
|
||||
this.setPitch(MAX_PITCH);
|
||||
adjustScaleForZoom();
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop camera from following the last selected subject
|
||||
*/
|
||||
private void untrackSubject() {
|
||||
public void untrackSubject() {
|
||||
if(target.get() != null) {
|
||||
trackBoat.stop();
|
||||
target.setValue(null);
|
||||
@@ -268,17 +267,81 @@ public class View3D extends Pane {
|
||||
* @param delta amount to change distance by
|
||||
*/
|
||||
public void updateDistance(double delta) {
|
||||
double distance = -this.distance.getZ() + delta;
|
||||
|
||||
if(distance <= FIRST_PERSON_LIMIT) {
|
||||
this.setDistance(FIRST_PERSON_LIMIT);
|
||||
} else if(distance > THIRD_PERSON_LIMIT) {
|
||||
this.setDistance(distance);
|
||||
double newDistance = -this.distance.getZ() + delta;
|
||||
if (target.get() == null){
|
||||
if (newDistance > MAX_ZOOM_LIMIT) {
|
||||
setDistance(MAX_ZOOM_LIMIT);
|
||||
} else if (newDistance <= ZOOM_IN_LIMIT) {
|
||||
setDistance(ZOOM_IN_LIMIT);
|
||||
} else {
|
||||
setDistance(newDistance);
|
||||
}
|
||||
} else if(newDistance <= ZOOM_IN_LIMIT) {
|
||||
setDistance(ZOOM_IN_LIMIT);
|
||||
} else if (newDistance > ZOOM_OUT_LIMIT){
|
||||
untrackSubject();
|
||||
setBirdsEye();
|
||||
setDistance(1050);
|
||||
updatePivot(new Translate(250, 0, 210));
|
||||
setYaw(0);
|
||||
} else {
|
||||
this.setDistance(distance);
|
||||
setDistance(newDistance);
|
||||
}
|
||||
adjustPitchForZoom();
|
||||
adjustScaleForZoom();
|
||||
}
|
||||
|
||||
/**
|
||||
* Adjusts the scale size of boats and markers as a user zooms in or out,
|
||||
* to smooth the change between third person to birds eye view.
|
||||
*/
|
||||
private void adjustScaleForZoom(){
|
||||
double itemScale = (((-distance.getZ() - (ZOOM_IN_LIMIT * 2)) /
|
||||
((THIRD_PERSON_LIMIT - (ZOOM_IN_LIMIT * 2)) /
|
||||
(1 - 0.1))) + 0.1);
|
||||
// if zoomed right in
|
||||
if (itemScale < 0.1){
|
||||
itemScale = 0.1;
|
||||
// if zoomed right out
|
||||
} else if (itemScale > 1) {
|
||||
itemScale = 1;
|
||||
}
|
||||
|
||||
// update scale
|
||||
for (Subject3D item : items) {
|
||||
item.setScale(itemScale);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adjusts the pitch as a user zooms in or out, to smooth the change
|
||||
* between third person to birds eye view.
|
||||
*/
|
||||
private void adjustPitchForZoom(){
|
||||
double pitch = (((-distance.getZ() - (ZOOM_IN_LIMIT*2)) /
|
||||
((THIRD_PERSON_LIMIT - (ZOOM_IN_LIMIT*2)) / (MAX_PITCH -
|
||||
MIN_PITCH))) + MIN_PITCH);
|
||||
|
||||
// if pitch is out of bounds, set it to the min/max
|
||||
if (pitch < MIN_PITCH) {
|
||||
pitch = MIN_PITCH;
|
||||
} else if (pitch > MAX_PITCH){
|
||||
pitch = MAX_PITCH;
|
||||
}
|
||||
setPitch(pitch);
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to be called when the zoom in key is pressed.
|
||||
*/
|
||||
public void zoomIn(){
|
||||
updateDistance(-ZOOM_PER_KEYPRESS);
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to be called when the zoom out key is pressed.
|
||||
*/
|
||||
public void zoomOut(){
|
||||
updateDistance(ZOOM_PER_KEYPRESS);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -289,6 +352,10 @@ public class View3D extends Pane {
|
||||
this.yaw.setAngle(yaw);
|
||||
}
|
||||
|
||||
public double getYaw(){
|
||||
return this.yaw.getAngle();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set elevation of camera
|
||||
* @param pitch in degrees
|
||||
@@ -297,6 +364,10 @@ public class View3D extends Pane {
|
||||
this.pitch.setAngle(-pitch);
|
||||
}
|
||||
|
||||
public double getPitch(){
|
||||
return this.pitch.getAngle();
|
||||
}
|
||||
|
||||
public void addAmbientLight(AmbientLight ambientLight) {
|
||||
this.world.getChildren().add(ambientLight);
|
||||
}
|
||||
@@ -304,4 +375,4 @@ public class View3D extends Pane {
|
||||
public void addPointLight(PointLight pointLight) {
|
||||
this.world.getChildren().add(pointLight);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
package visualiser.layout;
|
||||
|
||||
import javafx.animation.AnimationTimer;
|
||||
import javafx.beans.property.Property;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
import shared.model.Wind;
|
||||
|
||||
/**
|
||||
* Created by Gondr on 25/09/2017.
|
||||
*/
|
||||
public class WindCompass extends View3D {
|
||||
Subject3D compass;
|
||||
Subject3D windArrow;
|
||||
View3D view3D;
|
||||
Property<Wind> wind;
|
||||
|
||||
AnimationTimer followView3D = new AnimationTimer() {
|
||||
@Override
|
||||
public void handle(long now) {
|
||||
setYaw(180+ view3D.getYaw());
|
||||
setPitch(30 - view3D.getPitch());
|
||||
windArrow.setHeading(wind.getValue().getWindDirection().degrees());
|
||||
}
|
||||
};
|
||||
|
||||
public WindCompass(View3D view3D, Property<Wind> wind){
|
||||
super(false);
|
||||
this.wind = wind;
|
||||
this.view3D = view3D;
|
||||
this.followView3D.start();
|
||||
ObservableList<Subject3D> subjects = FXCollections.observableArrayList();
|
||||
this.setItems(subjects);
|
||||
compass = Assets3D.compass;
|
||||
windArrow = Assets3D.windArrow;
|
||||
subjects.addAll(compass, windArrow);
|
||||
this.setDistance(30);
|
||||
this.setPitch(90);
|
||||
this.setYaw(180);
|
||||
}
|
||||
|
||||
public void setHeading(double heading){
|
||||
windArrow.setHeading(heading);
|
||||
}
|
||||
|
||||
public void setCompassPitch(double pitch){
|
||||
setPitch(90d + pitch);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -605,7 +605,7 @@ public class ResizableRaceCanvas extends ResizableCanvas {
|
||||
|
||||
//Prepare to draw.
|
||||
gc.setLineWidth(1);
|
||||
gc.setFill(Color.DEEPSKYBLUE);
|
||||
gc.setFill(Color.web("#f1f1d4"));
|
||||
|
||||
|
||||
//Calculate the screen coordinates of the boundary.
|
||||
@@ -647,7 +647,7 @@ public class ResizableRaceCanvas extends ResizableCanvas {
|
||||
|
||||
//rounding lines
|
||||
if (isFullScreen){
|
||||
drawRoundingLines();
|
||||
// drawRoundingLines();
|
||||
drawRaceLine();
|
||||
}
|
||||
|
||||
@@ -794,18 +794,11 @@ public class ResizableRaceCanvas extends ResizableCanvas {
|
||||
}
|
||||
|
||||
private Color getLineColor(Leg leg) {
|
||||
for (VisualiserBoat boat : raceState.getBoats()) {
|
||||
if (boat.isClientBoat()) {
|
||||
if (boat.getCurrentLeg() == leg) {
|
||||
return Color.ORANGE;
|
||||
} else {
|
||||
return Color.MEDIUMAQUAMARINE;
|
||||
}
|
||||
}else{
|
||||
return Color.MEDIUMAQUAMARINE;
|
||||
}
|
||||
if(ThisBoat.getInstance().getLegNumber() == leg.getLegNumber()){
|
||||
return Color.ORANGE;
|
||||
}else{
|
||||
return Color.MEDIUMAQUAMARINE;
|
||||
}
|
||||
return Color.MEDIUMAQUAMARINE;
|
||||
}
|
||||
|
||||
private void drawArrowHead(GPSCoordinate start, GPSCoordinate end){
|
||||
|
||||
@@ -35,6 +35,14 @@ public class ThisBoat {
|
||||
}
|
||||
}
|
||||
|
||||
public int getLegNumber(){
|
||||
if(this.boat != null){
|
||||
return this.boat.getCurrentLeg().getLegNumber();
|
||||
}else{
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
public void setBoat(VisualiserBoat boat) {
|
||||
this.boat = boat;
|
||||
}
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
package visualiser.model;
|
||||
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.beans.property.*;
|
||||
import javafx.scene.paint.Color;
|
||||
import network.Messages.Enums.BoatStatusEnum;
|
||||
import shared.model.*;
|
||||
@@ -65,6 +62,7 @@ public class VisualiserBoat extends Boat {
|
||||
private ObjectProperty<GPSCoordinate> positionProperty;
|
||||
private ObjectProperty<Bearing> bearingProperty;
|
||||
private BooleanProperty hasCollided;
|
||||
private DoubleProperty healthProperty;
|
||||
|
||||
|
||||
/**
|
||||
@@ -78,6 +76,7 @@ public class VisualiserBoat extends Boat {
|
||||
|
||||
this.color = color;
|
||||
this.hasCollided = new SimpleBooleanProperty(false);
|
||||
this.healthProperty = new SimpleDoubleProperty(100);
|
||||
}
|
||||
|
||||
|
||||
@@ -284,4 +283,18 @@ public class VisualiserBoat extends Boat {
|
||||
public void setHasCollided(boolean hasCollided) {
|
||||
this.hasCollided.set(hasCollided);
|
||||
}
|
||||
|
||||
public DoubleProperty healthProperty() {
|
||||
return healthProperty;
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getHealth() {
|
||||
return healthProperty.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setHealth(double healthProperty) {
|
||||
this.healthProperty.set((int)healthProperty);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -248,7 +248,6 @@ public class VisualiserRaceState extends RaceState {
|
||||
|
||||
|
||||
if ((boat.getStatus() == BoatStatusEnum.DNF) || (boat.getStatus() == BoatStatusEnum.PRESTART) || (boat.getCurrentLeg().getLegNumber() < 0)) {
|
||||
|
||||
boat.setPlacing("-");
|
||||
|
||||
} else {
|
||||
|
||||
@@ -96,6 +96,10 @@ public class HttpMatchBrowserHost extends Thread {
|
||||
}
|
||||
}
|
||||
|
||||
public void sendStarted() throws IOException {
|
||||
sendHttp("http://api.umbrasheep.com/seng/match_started/");
|
||||
}
|
||||
|
||||
/**
|
||||
* THe host starts sending out heartbeat messages every 2 seconds.
|
||||
*/
|
||||
|
||||
@@ -297,6 +297,7 @@ public class ServerConnection implements RunnableWithFramePeriod {
|
||||
this.messageRouter.addRoute(MessageType.MARKROUNDING, incomingMessages);
|
||||
this.messageRouter.addRoute(MessageType.XMLMESSAGE, incomingMessages);
|
||||
this.messageRouter.addRoute(MessageType.ASSIGN_PLAYER_BOAT, incomingMessages);
|
||||
this.messageRouter.addRoute(MessageType.BOATSTATE, incomingMessages);
|
||||
this.messageRouter.removeDefaultRoute(); //We no longer want to keep un-routed messages.
|
||||
|
||||
|
||||
|
||||
@@ -111,4 +111,11 @@ public class GPSConverter {
|
||||
return Math.atan2(coord2.getX() - coord1.getX(), coord2.getY() - coord1.getY());
|
||||
}
|
||||
|
||||
public double getLongitudeFactor() {
|
||||
return longitudeFactor;
|
||||
}
|
||||
|
||||
public double getLatitudeFactor() {
|
||||
return latitudeFactor;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user