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;
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because one or more lines are too long
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,12 @@
|
||||
# Blender MTL File: 'Ocean V1.0.blend'
|
||||
# Material Count: 1
|
||||
|
||||
newmtl Material
|
||||
Ns 96.078431
|
||||
Ka 1.000000 1.000000 1.000000
|
||||
Kd 0.640000 0.640000 0.640000
|
||||
Ks 0.500000 0.500000 0.500000
|
||||
Ke 0.000000 0.000000 0.000000
|
||||
Ni 1.000000
|
||||
d 1.000000
|
||||
illum 2
|
||||
+31828
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,12 @@
|
||||
# Blender MTL File: 'Ocean V1.0.blend'
|
||||
# Material Count: 1
|
||||
|
||||
newmtl Material
|
||||
Ns 96.078431
|
||||
Ka 1.000000 1.000000 1.000000
|
||||
Kd 0.640000 0.640000 0.640000
|
||||
Ks 0.500000 0.500000 0.500000
|
||||
Ke 0.000000 0.000000 0.000000
|
||||
Ni 1.000000
|
||||
d 1.000000
|
||||
illum 2
|
||||
+31821
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,12 @@
|
||||
# Blender MTL File: 'Ocean V1.0.blend'
|
||||
# Material Count: 1
|
||||
|
||||
newmtl Material
|
||||
Ns 96.078431
|
||||
Ka 1.000000 1.000000 1.000000
|
||||
Kd 0.640000 0.640000 0.640000
|
||||
Ks 0.500000 0.500000 0.500000
|
||||
Ke 0.000000 0.000000 0.000000
|
||||
Ni 1.000000
|
||||
d 1.000000
|
||||
illum 2
|
||||
+31836
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,12 @@
|
||||
# Blender MTL File: 'Ocean V1.0.blend'
|
||||
# Material Count: 1
|
||||
|
||||
newmtl Material
|
||||
Ns 96.078431
|
||||
Ka 1.000000 1.000000 1.000000
|
||||
Kd 0.640000 0.640000 0.640000
|
||||
Ks 0.500000 0.500000 0.500000
|
||||
Ke 0.000000 0.000000 0.000000
|
||||
Ni 1.000000
|
||||
d 1.000000
|
||||
illum 2
|
||||
+31816
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,12 @@
|
||||
# Blender MTL File: 'Ocean V1.0.blend'
|
||||
# Material Count: 1
|
||||
|
||||
newmtl Material
|
||||
Ns 96.078431
|
||||
Ka 1.000000 1.000000 1.000000
|
||||
Kd 0.640000 0.640000 0.640000
|
||||
Ks 0.500000 0.500000 0.500000
|
||||
Ke 0.000000 0.000000 0.000000
|
||||
Ni 1.000000
|
||||
d 1.000000
|
||||
illum 2
|
||||
+31832
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,12 @@
|
||||
# Blender MTL File: 'Ocean V1.0.blend'
|
||||
# Material Count: 1
|
||||
|
||||
newmtl Material
|
||||
Ns 96.078431
|
||||
Ka 1.000000 1.000000 1.000000
|
||||
Kd 0.640000 0.640000 0.640000
|
||||
Ks 0.500000 0.500000 0.500000
|
||||
Ke 0.000000 0.000000 0.000000
|
||||
Ni 1.000000
|
||||
d 1.000000
|
||||
illum 2
|
||||
+31825
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,12 @@
|
||||
# Blender MTL File: 'Ocean V1.0.blend'
|
||||
# Material Count: 1
|
||||
|
||||
newmtl Material
|
||||
Ns 96.078431
|
||||
Ka 1.000000 1.000000 1.000000
|
||||
Kd 0.640000 0.640000 0.640000
|
||||
Ks 0.500000 0.500000 0.500000
|
||||
Ke 0.000000 0.000000 0.000000
|
||||
Ni 1.000000
|
||||
d 1.000000
|
||||
illum 2
|
||||
+31827
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,12 @@
|
||||
# Blender MTL File: 'Ocean V1.0.blend'
|
||||
# Material Count: 1
|
||||
|
||||
newmtl Material
|
||||
Ns 96.078431
|
||||
Ka 1.000000 1.000000 1.000000
|
||||
Kd 0.640000 0.640000 0.640000
|
||||
Ks 0.500000 0.500000 0.500000
|
||||
Ke 0.000000 0.000000 0.000000
|
||||
Ni 1.000000
|
||||
d 1.000000
|
||||
illum 2
|
||||
+31828
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,12 @@
|
||||
# Blender MTL File: 'Ocean V1.0.blend'
|
||||
# Material Count: 1
|
||||
|
||||
newmtl Material
|
||||
Ns 96.078431
|
||||
Ka 1.000000 1.000000 1.000000
|
||||
Kd 0.640000 0.640000 0.640000
|
||||
Ks 0.500000 0.500000 0.500000
|
||||
Ke 0.000000 0.000000 0.000000
|
||||
Ni 1.000000
|
||||
d 1.000000
|
||||
illum 2
|
||||
+31827
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,12 @@
|
||||
# Blender MTL File: 'Ocean V1.0.blend'
|
||||
# Material Count: 1
|
||||
|
||||
newmtl Material
|
||||
Ns 96.078431
|
||||
Ka 1.000000 1.000000 1.000000
|
||||
Kd 0.640000 0.640000 0.640000
|
||||
Ks 0.500000 0.500000 0.500000
|
||||
Ke 0.000000 0.000000 0.000000
|
||||
Ni 1.000000
|
||||
d 1.000000
|
||||
illum 2
|
||||
+31822
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,12 @@
|
||||
# Blender MTL File: 'Ocean V1.0.blend'
|
||||
# Material Count: 1
|
||||
|
||||
newmtl Material
|
||||
Ns 96.078431
|
||||
Ka 1.000000 1.000000 1.000000
|
||||
Kd 0.640000 0.640000 0.640000
|
||||
Ks 0.500000 0.500000 0.500000
|
||||
Ke 0.000000 0.000000 0.000000
|
||||
Ni 1.000000
|
||||
d 1.000000
|
||||
illum 2
|
||||
+31821
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,12 @@
|
||||
# Blender MTL File: 'Ocean V1.0.blend'
|
||||
# Material Count: 1
|
||||
|
||||
newmtl Material
|
||||
Ns 96.078431
|
||||
Ka 1.000000 1.000000 1.000000
|
||||
Kd 0.640000 0.640000 0.640000
|
||||
Ks 0.500000 0.500000 0.500000
|
||||
Ke 0.000000 0.000000 0.000000
|
||||
Ni 1.000000
|
||||
d 1.000000
|
||||
illum 2
|
||||
+31822
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,12 @@
|
||||
# Blender MTL File: 'Ocean V1.0.blend'
|
||||
# Material Count: 1
|
||||
|
||||
newmtl Material
|
||||
Ns 96.078431
|
||||
Ka 1.000000 1.000000 1.000000
|
||||
Kd 0.640000 0.640000 0.640000
|
||||
Ks 0.500000 0.500000 0.500000
|
||||
Ke 0.000000 0.000000 0.000000
|
||||
Ni 1.000000
|
||||
d 1.000000
|
||||
illum 2
|
||||
+31821
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,12 @@
|
||||
# Blender MTL File: 'Ocean V1.0.blend'
|
||||
# Material Count: 1
|
||||
|
||||
newmtl Material
|
||||
Ns 96.078431
|
||||
Ka 1.000000 1.000000 1.000000
|
||||
Kd 0.640000 0.640000 0.640000
|
||||
Ks 0.500000 0.500000 0.500000
|
||||
Ke 0.000000 0.000000 0.000000
|
||||
Ni 1.000000
|
||||
d 1.000000
|
||||
illum 2
|
||||
+31823
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,12 @@
|
||||
# Blender MTL File: 'Ocean V1.0.blend'
|
||||
# Material Count: 1
|
||||
|
||||
newmtl Material
|
||||
Ns 96.078431
|
||||
Ka 1.000000 1.000000 1.000000
|
||||
Kd 0.640000 0.640000 0.640000
|
||||
Ks 0.500000 0.500000 0.500000
|
||||
Ke 0.000000 0.000000 0.000000
|
||||
Ni 1.000000
|
||||
d 1.000000
|
||||
illum 2
|
||||
+31824
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,12 @@
|
||||
# Blender MTL File: 'Ocean V1.0.blend'
|
||||
# Material Count: 1
|
||||
|
||||
newmtl Material
|
||||
Ns 96.078431
|
||||
Ka 1.000000 1.000000 1.000000
|
||||
Kd 0.640000 0.640000 0.640000
|
||||
Ks 0.500000 0.500000 0.500000
|
||||
Ke 0.000000 0.000000 0.000000
|
||||
Ni 1.000000
|
||||
d 1.000000
|
||||
illum 2
|
||||
+31823
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,12 @@
|
||||
# Blender MTL File: 'Ocean V1.0.blend'
|
||||
# Material Count: 1
|
||||
|
||||
newmtl Material
|
||||
Ns 96.078431
|
||||
Ka 1.000000 1.000000 1.000000
|
||||
Kd 0.640000 0.640000 0.640000
|
||||
Ks 0.500000 0.500000 0.500000
|
||||
Ke 0.000000 0.000000 0.000000
|
||||
Ni 1.000000
|
||||
d 1.000000
|
||||
illum 2
|
||||
+31818
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,12 @@
|
||||
# Blender MTL File: 'Ocean V1.0.blend'
|
||||
# Material Count: 1
|
||||
|
||||
newmtl Material
|
||||
Ns 96.078431
|
||||
Ka 1.000000 1.000000 1.000000
|
||||
Kd 0.640000 0.640000 0.640000
|
||||
Ks 0.500000 0.500000 0.500000
|
||||
Ke 0.000000 0.000000 0.000000
|
||||
Ni 1.000000
|
||||
d 1.000000
|
||||
illum 2
|
||||
+31830
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,12 @@
|
||||
# Blender MTL File: 'Ocean V1.0.blend'
|
||||
# Material Count: 1
|
||||
|
||||
newmtl Material
|
||||
Ns 96.078431
|
||||
Ka 1.000000 1.000000 1.000000
|
||||
Kd 0.640000 0.640000 0.640000
|
||||
Ks 0.500000 0.500000 0.500000
|
||||
Ke 0.000000 0.000000 0.000000
|
||||
Ni 1.000000
|
||||
d 1.000000
|
||||
illum 2
|
||||
+31815
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,12 @@
|
||||
# Blender MTL File: 'Ocean V1.0.blend'
|
||||
# Material Count: 1
|
||||
|
||||
newmtl Material
|
||||
Ns 96.078431
|
||||
Ka 1.000000 1.000000 1.000000
|
||||
Kd 0.640000 0.640000 0.640000
|
||||
Ks 0.500000 0.500000 0.500000
|
||||
Ke 0.000000 0.000000 0.000000
|
||||
Ni 1.000000
|
||||
d 1.000000
|
||||
illum 2
|
||||
+31831
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,12 @@
|
||||
# Blender MTL File: 'Ocean V1.0.blend'
|
||||
# Material Count: 1
|
||||
|
||||
newmtl Material
|
||||
Ns 96.078431
|
||||
Ka 1.000000 1.000000 1.000000
|
||||
Kd 0.640000 0.640000 0.640000
|
||||
Ks 0.500000 0.500000 0.500000
|
||||
Ke 0.000000 0.000000 0.000000
|
||||
Ni 1.000000
|
||||
d 1.000000
|
||||
illum 2
|
||||
+31827
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,12 @@
|
||||
# Blender MTL File: 'Ocean V1.0.blend'
|
||||
# Material Count: 1
|
||||
|
||||
newmtl Material
|
||||
Ns 96.078431
|
||||
Ka 1.000000 1.000000 1.000000
|
||||
Kd 0.640000 0.640000 0.640000
|
||||
Ks 0.500000 0.500000 0.500000
|
||||
Ke 0.000000 0.000000 0.000000
|
||||
Ni 1.000000
|
||||
d 1.000000
|
||||
illum 2
|
||||
+31819
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user