Compare commits

...

114 Commits

Author SHA1 Message Date
Calum c0bd498f1b Added debugs for missing xml 2017-09-27 02:50:36 +13:00
Calum b18d9e8573 Added empty cucumber
#test
2017-09-27 02:04:58 +13:00
Calum e9881bb24a Added per map max player count. Handles case when map cannot fit players behind start mark. Added initial implementation for spacing out yachts.
#implement  #story[1275]
2017-09-27 02:04:24 +13:00
Calum ab5ad58237 Merge branch 'develop' into story1275_host_customization
# Conflicts:
#	src/main/java/seng302/visualiser/GameView3D.java
2017-09-26 23:37:59 +13:00
Calum df7264cc1f Added per map max player count. Handles case when map cannot fit players behind start mark. Added initial implementation for spacing out yachts.
#implement.
2017-09-26 23:34:19 +13:00
Zhi You Tan 5248921576 Multiple fixes:
- fix last button no longer default focused when loading keybinding dialog
- fix click and mouse exit still focused on button
- closing keybinding does not focus on chat anymore

#story[1273]
2017-09-26 22:52:58 +13:00
Michael Rausch 2dcdd1c248 Merge branch '1273_Changing_Cameras' into 'develop'
1273 changing cameras

# Multiple Camera Views

## 3 Camera Views Implemented
1. Normal 3D (Isometric)
2. Top Down Perspective
3. Chase Cam (Over the Shoulder)

- Isometric and Top Down Perspectives can Pan within limits.
- Chase Cam rotates the camera around the boat. (While still following it)
- All camera views can Zoom.

# Testing

No testing completed other than manually testing myself. A proper manual test should be completed by the merger.

See merge request !76
2017-09-26 18:36:59 +13:00
Alistair McIntyre 9b00f76907 removed print statements and added documentation.
tags : #story[1273]
2017-09-26 18:36:13 +13:00
Alistair McIntyre f11c457d28 removed print statements
tags : #story[1273]
2017-09-26 18:31:47 +13:00
Michael Rausch 9e4fa30787 Merge branch 'issue68_boat_not_turning_on_starbord_vmg' into 'develop'
the boat can now turn after snapping to starboard vmg #story[1273]



See merge request !75
2017-09-26 18:11:24 +13:00
Alistair McIntyre 99ce4fa11d Merged develop in and reimplemented keybindings for moving the camera.
tags : #story[1273]
2017-09-26 18:01:39 +13:00
Alistair McIntyre 69d1fa9488 Merge branch 'develop' into 1273_Changing_Cameras 2017-09-26 17:36:40 +13:00
Alistair McIntyre 66e6a8a2a4 - Small Changes
tags : #story[1273]
2017-09-26 17:35:36 +13:00
Peter Galloway dd43097677 the boat can now turn after snapping to starboard vmg #story[1273] 2017-09-26 17:07:55 +13:00
Alistair McIntyre 1a53579317 - Isometric Camera
- Zoom Bounds added.
 - Camera Panning Bounds added. Need to be tested with extra maps.

tags : #story[1273]
2017-09-26 16:57:07 +13:00
Haoming Yin 132a729758 Temporarily added more options in key binding dialog. But still need to implement binding after camera story has been merged into develop
tags: #story[1273]
2017-09-26 15:49:16 +13:00
alistairjmcintyre 2e7487fdfc - Chase Camera:
- Has panning bounds, zoom bounds, and general tidy up.
 - Now correctly observes the boat object rather than getting information from both the boat object AND the client yacht model.

Top Down Camera:
 - Can only pan within certain bounds now, and will continue to follow the boat regardless.
 - Can only zoom within certain bounds now.

Isometric Camera:
 - Nothing changed.

#tags [1273]
2017-09-26 15:06:21 +13:00
Haoming Yin 1bd4db73cd Issue 66: client side error pop ups use default javaFx style
- created JFeonix style pop up to replace the default one

tags: #story[1273]
2017-09-26 14:44:37 +13:00
Calum e990c68d40 #document 2017-09-26 01:12:43 +13:00
Calum 83871a0336 Fixed boat trials rendering abnormally
#issue[65] #fix
2017-09-26 01:07:02 +13:00
Calum 6cba024d64 Made changes to the shape of boat trials. 2017-09-26 00:59:04 +13:00
Calum 51747e2d13 Refactored the 2D and 3D game view class setups. Made scaling more logical.
#refactor #story[1275]
2017-09-26 00:55:28 +13:00
Calum b3981b19e0 Merge branch 'develop' into story1275_host_customization
# Conflicts:
#	src/main/java/seng302/gameServer/GameState.java
#	src/main/java/seng302/gameServer/ServerToClientThread.java
#	src/main/java/seng302/model/ServerYacht.java
#	src/main/java/seng302/visualiser/ClientToServerThread.java
#	src/main/java/seng302/visualiser/controllers/LobbyController.java
#	src/test/java/seng302/models/YachtTest.java
2017-09-25 23:25:33 +13:00
Calum 3d7a64068f Fixed scaling issues.
#fix
2017-09-25 23:19:03 +13:00
Calum c12f7408ad experimenting with map scaling
#implement
2017-09-25 22:57:45 +13:00
Calum 7027de80c4 Fixed issues with correct protocol implementation.
#issue[64] #fix #testmanual
2017-09-25 22:09:26 +13:00
Calum 35b50d1436 Refactored 2d game view distance calculations to better size and sale the map,
#implement #refactor #story[1275]
2017-09-25 21:06:18 +13:00
William Muir 5c50e77efa Merge branch 'Story1278_keybindings' into 'develop'
Story1278 keybindings

# Changes

- Added customised action key binding for all boat actions
- Users can select the turning mode (continuously turning mode, and the default turning mode)
- Refined UI design for key bindings
- Added animation which mouse enters/exits the setting button, and also prompt text to guide users to press key
- Popup window to inform users if their key assignment is successful or not

# Testing

## Manual testing log as below (all passed according to the assigner)
- if a player toggles the turning mode toggle button, the labels for upwind and downwind will be changed correspondingly
- if a player clicks the "reset" button, all the key binding settings will be reset to default
- if a player assigns a key which is already is in use, a popup box will inform the assignment is failed, and action will be changed back
- if a player assigns a key successfully, a popup will inform the success, and the new key binding will appear on the button
- A player can change their key bindings before the race starts and during the race


See merge request !73
2017-09-25 17:11:12 +13:00
Zhi You Tan 0e93be7b36 Fixed send button traversable issue.
- removed sensitive characters.

#story[1278] #pair[hyi25, zyt10]
2017-09-25 17:06:36 +13:00
Zhi You Tan 191b818e38 Added close button for keyBindingDialog.
- fixed that you cannot bind the key you are using.

#story[1278] #pair[hyi25, zyt10]
2017-09-25 16:31:28 +13:00
Alistair McIntyre 00b09997b0 - Added Camera panning to chase camera mode.
tags : #story[1273]
2017-09-25 16:28:37 +13:00
Alistair McIntyre d250c635d8 - Adjusted server tick rate to test smoothing
tags : #story[1273]
2017-09-25 15:32:13 +13:00
Alistair McIntyre 376c4d25a8 Merge branch 'develop' into 1273_Changing_Cameras
# Conflicts:
#	src/main/java/seng302/model/ClientYacht.java
2017-09-25 14:40:19 +13:00
Alistair McIntyre e66abb4340 - An attempt at chase cam smoothing. needs work.
tags : #story[1273]
2017-09-25 14:39:09 +13:00
Alistair McIntyre a19e191684 - Chase cam kind of working, need to find a fix for the abrupt direction change.
tags : #story[1273]
2017-09-25 14:03:47 +13:00
Haoming Yin 19db6668da Fixed merge conflicts in server yacht
tags #story[1278]
2017-09-25 11:09:14 +13:00
Haoming Yin 44275aec04 Merge branch 'develop' into Story1278_keybindings
# Conflicts:
#	src/main/java/seng302/model/ServerYacht.java
2017-09-25 11:06:28 +13:00
Zhi You Tan 64245833cd Merge branch 'Story1274_custom_boat_stats' into 'develop'
Story1274 custom boat stats

# Changes
- Added 3 areas for game play variation with the boats (stats)
    - Max speed
    - Handling
    - Acceleration
- Added stat bars on the customize boat screen so players can see the stats

# Testing
- Manual testing
- No JUnits made since no real processing methods were added


See merge request !72
2017-09-24 18:53:15 +13:00
Kusal Ekanayake aa0149b9a7 Merge branch 'develop' into Story1274_custom_boat_stats 2017-09-24 18:08:54 +13:00
Kusal Ekanayake f6b41f0513 Fixes Issue #59 2017-09-24 18:06:26 +13:00
Calum 9b00ba654a Added a race importer. Added imported races to visualizer. Made it so that the host sets the race. Refactored server to no longer be dependant on a specific race. Tested functionality of map manually. Some bugs found and listed below.
#implement #testmanual #story[1275]

Known bugs:
 * Can't move
 * Map is off center in lobby view.
 * 3D Map is off center
2017-09-23 22:45:53 +12:00
Haoming Yin 8dfdb228e9 Fixed a bug that game client tries to send turning mode packet when there is no socketThread
- add a if statement to check if socketThread is initialized before sending packet.

#story[1278]
2017-09-23 21:04:57 +12:00
Haoming Yin 1042817e4e Snackbar's color can be changed according to the message type
- load a separate css file to change the color
- if the assignment failed, the snackbar prompt will be red, otherwise theme color

#story[1278]
2017-09-23 20:59:29 +12:00
Haoming Yin 066557584f Implemented turning mode toggle
- when the mode is toggled, a boat action package will be sent to notify server
to change the boat's turning mode
- turning mode toggle is now fully functional

#story[1245]
2017-09-23 19:37:13 +12:00
Haoming Yin 4011295b8b Slightly optimised code style and add more functionality
- optimised UI
- check conflicts when change key bind if the key has already been in use
- abstract keybind as a separate singleton class so all class can access it
- [WIP] turning mode is need to be finished

#story[1245]
2017-09-23 17:52:48 +12:00
Kusal Ekanayake 0a885dd8fd Clean up changes
#story[1274]
2017-09-23 15:37:16 +12:00
Kusal Ekanayake e9b50038a9 Balance changes. Always wanted to use that as a commit message.
#story[1274]
2017-09-23 15:09:22 +12:00
Kusal Ekanayake 364264377a Fixed merge errors and reimplemented handling multiplier
#story[1274]
2017-09-23 15:04:11 +12:00
Kusal Ekanayake 9112183ac3 Merge branch 'develop' into Story1274_custom_boat_stats
# Conflicts:
#	src/main/java/seng302/model/ServerYacht.java
#	src/main/java/seng302/visualiser/controllers/dialogs/BoatCustomizeController.java
2017-09-23 14:48:50 +12:00
Zhi You Tan 957821f1f2 [WIP] Created a snackbar for notification. Currently used for keybinding success/fail. Need to show red if fails.
#story[1278]
2017-09-23 01:39:26 +12:00
Zhi You Tan 094eb4c1cf Merge remote-tracking branch 'origin/develop' into Story1278_keybindings
# Conflicts:
#	src/main/resources/views/RaceView.fxml
2017-09-22 21:22:06 +12:00
William Muir 607acff7c6 Merge branch 'Custom_boat_selection' into 'develop'
Custom boat selection

# Story 1274 Feature Custom Boat Selection
## Changes
- 2 new boats!
- A boat selector in the customisation for boat dialog
- The potential for everyone to use whatever boats and for everyone to see

## Testing
- Basic JUnit tests added to test basic util methods added to the BoatMeshType class
- Manual testing completed

See merge request !71
2017-09-22 21:14:12 +12:00
William Muir 22fdf1e4ac Changed the boatType attribute from all around the place from String lit to enum
#story[1274]
2017-09-22 21:00:28 +12:00
William Muir da8c91f5c1 Review fixes for merge request.
PlayerCell now takes in the yacht for construction rather than taking in a whole lot of values extracted from the yacht

Reduced boiler plate in BoatCustomizeController

#story[1274]
2017-09-22 20:44:06 +12:00
Kusal Ekanayake 52dc7a956d Turned handling into a multiplier.
#story[1274]
2017-09-22 17:42:32 +12:00
Kusal Ekanayake 9f64b2380d Implemented acceleration and full loading bars.
#story[1274]
2017-09-22 17:28:42 +12:00
Kusal Ekanayake b05580f018 Worked on making a visual component to the stats. Need to implement acceleration.
#story[1274]
2017-09-22 16:45:10 +12:00
Haoming Yin c20c6fb264 [WIP] Added new toggle for steer turning mode and submit and reset button (restore the setting to default)
- minor fix of UI elements to make the font smaller
- centred labels and buttons

tags: #story[1246]
2017-09-22 13:53:36 +12:00
Kusal Ekanayake faeece27ff Started working on individual boat stats. Already modified turning rate. Need to add a vsual component.
#story[1274]
2017-09-22 13:52:35 +12:00
Calum 6ca75b2cac Changed default race 2017-09-21 15:08:39 +12:00
Alistair McIntyre 6ff309a40c - Perspective Camera Works
- Top Down Camera Works
- Started on chase cam but the math is a bit tricky.

tags : #story[1273]
2017-09-21 14:40:35 +12:00
Calum 40a7f9bc5b New server creation view created. Added templates for custom races. Updated xml generator to remove all hard coded values. Updated XMLParser to parse custom race files. No unit tests exists currently.
#implement #story[1275]
2017-09-21 12:59:37 +12:00
Kusal Ekanayake c4a6113f6c Minor bug fixes (like all enemy sails being toggled in when they should be out)
#story[1274]
2017-09-20 21:07:49 +12:00
Kusal Ekanayake 307e79ecfc Completed working boat selection screen.
When a user selects a different boat, it is sent to all other clients and updates accordingly. Boats are all shown with their correct models in game.

#story[1274]
2017-09-20 20:46:23 +12:00
Kusal Ekanayake 7d8a6afa5f Merge branch 'new_meshes' into Custom_boat_selection 2017-09-20 19:40:43 +12:00
Peter Galloway ea0be5e952 Added pirate ship meshes to application. Updated boat model to allow for jib sails and a fixed sail. #story[1274] 2017-09-20 17:56:07 +12:00
Peter Galloway 7197bc2bee created meshes for pirate ship #story[1274] 2017-09-20 17:33:32 +12:00
Calum fba522d0c3 Fixed BoatMeshType enum names.
#fix
2017-09-20 16:44:38 +12:00
Calum 0e829874c2 Fixed BoatMeshType enum names.
#fix
2017-09-20 16:43:04 +12:00
Calum c5d56065b6 Fixed cat ate a meringue sail rotation.
#fix #story[1274]
2017-09-20 16:42:26 +12:00
Kusal Ekanayake 410d765745 Started working on the boat selection screen.
Customised the boat customisation UI to contain it. Need to have another boat to test whether we can switch boats and also if the messages get sent correctly.

#story[1274]
2017-09-20 16:36:27 +12:00
Calum fe76e85c71 Merge branch 'develop' into new_meshes 2017-09-20 15:59:01 +12:00
Calum 9d61a43bd7 Added catamaran mesh to possible boat meshes. Made catamaran the default boat.
#implement #story[1274]
2017-09-20 15:58:22 +12:00
Kusal Ekanayake c39582de5c Updated PartyParrot logo 2017-09-20 15:41:20 +12:00
Zhi You Tan d4d7ddf8e2 Keybinding now works in the actual race. A map of keybind is shared between GameClient and KeyBindDialogController.
#story[1278]
2017-09-20 11:02:13 +12:00
Zhi You Tan a1933c2869 - Created keybindingcontroller which can detect keypress and can save the keybind throughout the app
- Changed keybindingglyph to keyboard icon
- Fix button hover CSS to change text fill

#story[1278]
2017-09-20 02:42:02 +12:00
Zhi You Tan 8084a61333 Fix keybinding dialog creating on top of another keybinding dialog.
#story[1278]
2017-09-19 23:41:06 +12:00
Zhi You Tan 03f5f91043 Done some CSS on keybindingdialog
#story[1278]
2017-09-19 19:49:36 +12:00
Peter Galloway 9ed52a1225 created catamaran mesh #story[1274] 2017-09-19 19:43:03 +12:00
Zhi You Tan 027324cc4f - Fixed JFXDialog initialised in lobby, raceview, serverlist controller by default. Now, dialog only appears when called.
- [WIP] Created keybindingdialog fxml. CSS not yet done.
 - Removed top most anchorpane on raceview so parent is stackpane like other views.

#story[1278]
2017-09-19 18:40:01 +12:00
Calum da263355f4 Changed raceview background.
#fix
2017-09-19 14:55:26 +12:00
Calum ebecd25ed2 Merge remote-tracking branch 'origin/develop' into develop 2017-09-19 14:54:15 +12:00
Calum 0f5137c2b6 Fixed the orientation of .stl files.
#fix
2017-09-19 14:52:02 +12:00
Kusal Ekanayake 73799954e4 Fixed tests that failed when running on lower end computers. Needed to add a couple of thread.sleeps 2017-09-15 12:47:40 +12:00
Haoming Yin edfeb2b287 Merge remote-tracking branch 'origin/NewUI_merge' into NewUI_merge 2017-09-14 15:40:26 +12:00
Haoming Yin 0355784000 Broadcast messages when boats pass legs or a token is picked up or expired.
tags: #story[1250] #pair[hyi25, zyt10]
2017-09-14 15:40:12 +12:00
Alistair McIntyre 02df69b7b4 - Merged Dev into branch
Tags: #story[1245]
2017-09-14 15:27:10 +12:00
Alistair McIntyre 242132b800 Merge remote-tracking branch 'origin/develop' into NewUI_merge
# Conflicts:
#	src/main/resources/views/RaceView.fxml
2017-09-14 15:26:57 +12:00
Alistair McIntyre 3a671d4ed0 - Added Values to Finish Dialog.
Tags: #story[1245]
2017-09-14 15:24:58 +12:00
Alistair McIntyre 482d987839 - Fixed Null Pointer
- Build Should pass

Tags: #story[1245]
2017-09-14 15:10:48 +12:00
Haoming Yin cc124b2d19 Merge remote-tracking branch 'origin/NewUI_merge' into NewUI_merge
# Conflicts:
#	src/main/java/seng302/gameServer/GameState.java
2017-09-14 14:50:49 +12:00
Haoming Yin b3320ad805 Fixed server message sender
tags: #story[1246]
2017-09-14 14:49:25 +12:00
Alistair McIntyre 33779ad5c1 Merge remote-tracking branch 'origin/NewUI_merge' into NewUI_merge
# Conflicts:
#	src/main/resources/views/RaceView.fxml
2017-09-14 14:42:57 +12:00
Alistair McIntyre 3a41c27d8d - Race Finish Dialog showing up, unsure if its actually showing the correct ordering or not.
tags : #story[1245]
2017-09-14 14:42:25 +12:00
Calum e24203904b Removed print statements.
#chore
2017-09-14 14:37:22 +12:00
Calum eb188495ce Fixed position issues on exit arrows.
#implement #story[1266] #fix
2017-09-14 14:32:45 +12:00
Calum 62a7e2b8fa Fixed position issues on entry arrows.
#implement #story[1266] #fix
2017-09-14 14:21:04 +12:00
Michael Rausch acd0e790fe Added method to send server debug messages to players
Tags: #story[1246]
2017-09-14 14:14:21 +12:00
Zhi You Tan 46013474c0 Merge remote-tracking branch 'origin/NewUI_merge' into NewUI_merge 2017-09-14 13:37:01 +12:00
Alistair McIntyre bf427f24d3 - Created a Race Finish Dialog.
tags : #story[1245]
2017-09-14 13:34:48 +12:00
Alistair McIntyre 7d0a47446d - Fixed bug in customize dialog.
tags : #story[1245]
2017-09-14 13:02:31 +12:00
Alistair McIntyre 889098bb50 - Commented out broken test (non-deterministic thing)
- Merged 3d factory branch in

tags : #story[1245]
2017-09-14 12:46:17 +12:00
Alistair McIntyre 6223a8fc0b Merge remote-tracking branch 'origin/story1266_3d_model_factory' into NewUI_merge
# Conflicts:
#	src/main/java/seng302/visualiser/GameView3D.java
2017-09-14 12:40:20 +12:00
Calum 391bd33548 Fixed position issues on first 2 mark arrows.
#implement #story[1266]
2017-09-14 12:38:36 +12:00
Alistair McIntyre 7e0c2abbfd - Fixed a shutdown bug that left the game process running long after the window shut
- Changed SVG Icon for volume toggle to something a bit nicer

tags : #story[1245]
2017-09-14 12:31:49 +12:00
Calum 42a5e86bf8 Merge remote-tracking branch 'origin/NewUI_merge' into story1266_3d_model_factory
# Conflicts:
#	src/main/java/seng302/visualiser/GameView3D.java
2017-09-14 12:06:19 +12:00
Calum 20d73d8f2f Removed dead code from fxObjects and GameView 3D
#chore
2017-09-14 12:05:20 +12:00
Calum 0a62c538ca Added 3D mark arrows and cleaned up other classes. 2017-09-14 11:28:50 +12:00
Zhi You Tan 24a04aa530 Fix the wind direction arrow rotation.
#story[1245]
2017-09-14 01:05:52 +12:00
Haoming Yin 7ee6a09626 Fixed that wind speed did not updated frequently and UI polishing.
- added shadow for raceview boxes
- split up message history and input text block
- changed timer font

#story[1245]
2017-09-13 21:11:40 +12:00
Calum d5ce61a0ff Merge branch 'NewUI_merge' into story1266_3d_model_factory
# Conflicts:
#	src/main/java/seng302/visualiser/controllers/RaceViewController.java
2017-09-12 17:09:47 +12:00
Peter Galloway 71f626f57e fixed sail rotation broken from port to 3d #story[1266] 2017-09-12 16:30:20 +12:00
Calum 8dc3e54186 Merge remote-tracking branch 'origin/story1266_3d_model_factory' into story1266_3d_model_factory 2017-09-12 14:11:11 +12:00
Calum 8fd35392b0 Added experimental assets for water.
#story[1266] #implement
2017-09-12 14:11:00 +12:00
132 changed files with 5058 additions and 3367 deletions
+45 -52
View File
@@ -4,17 +4,14 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.Set;
import javafx.scene.paint.Color;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;
import org.xml.sax.InputSource;
import seng302.gameServer.messages.BoatAction;
import seng302.gameServer.messages.BoatStatus;
import seng302.gameServer.messages.ChatterMessage;
@@ -33,10 +30,11 @@ import seng302.model.ServerYacht;
import seng302.model.mark.CompoundMark;
import seng302.model.mark.Mark;
import seng302.model.mark.MarkOrder;
import seng302.model.stream.xml.parser.RaceXMLData;
import seng302.model.token.Token;
import seng302.model.token.TokenType;
import seng302.utilities.GeoUtility;
import seng302.utilities.XMLParser;
import seng302.visualiser.fxObjects.assets_3D.BoatMeshType;
/**
* A Static class to hold information about the current state of the game (model)
@@ -75,7 +73,6 @@ public class GameState implements Runnable {
private static Boolean customizationFlag; // dirty flag to tell if a player has customized their boat.
private static Boolean playerHasLeftFlag;
private static String hostIpAddress;
private static List<Player> players;
private static Map<Integer, ServerYacht> yachts;
private static Boolean isRaceStarted;
@@ -94,14 +91,13 @@ public class GameState implements Runnable {
private static Map<Player, String> playerStringMap = new HashMap<>();
public GameState(String hostIpAddress) {
public GameState() {
windDirection = 180d;
windSpeed = 10000d;
yachts = new HashMap<>();
tokensInPlay = new ArrayList<>();
marks = new HashSet<>();
players = new ArrayList<>();
GameState.hostIpAddress = hostIpAddress;
customizationFlag = false;
playerHasLeftFlag = false;
speedMultiplier = 1.0;
@@ -109,34 +105,20 @@ public class GameState implements Runnable {
isRaceStarted = false;
//set this when game stage changes to prerace
previousUpdateTime = System.currentTimeMillis();
markOrder = new MarkOrder(); //This could be instantiated at some point with a select map?
newMessageListeners = new ArrayList<>();
allTokens = makeTokens();
resetStartTime();
new Thread(this, "GameState").start(); //Run the auto updates on the game state
marks = new MarkOrder().getAllMarks();
setCourseLimit("/server_config/race.xml");
}
private void setCourseLimit(String url) {
DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
documentBuilderFactory.setNamespaceAware(true);
DocumentBuilder documentBuilder;
Document document = null;
try {
documentBuilder = documentBuilderFactory.newDocumentBuilder();
document = documentBuilder.parse(new InputSource(getClass().getResourceAsStream(url)));
} catch (Exception e) {
// sorry, we have to catch general one, otherwise we have to catch five different exceptions.
logger.trace("Failed to load course limit for boundary collision detection.", e);
public static void setRace(RaceXMLData raceXMLData) {
markOrder = new MarkOrder(raceXMLData);
for (CompoundMark compoundMark : raceXMLData.getCompoundMarks().values()){
marks.addAll(compoundMark.getMarks());
}
courseLimit = XMLParser.parseRace(document).getCourseLimit();
courseLimit = raceXMLData.getCourseLimit();
}
/**
* Make a pre defined set of tokensInPlay. //TODO wmu16 - Should read from some file for each
* race ideally
@@ -151,10 +133,6 @@ public class GameState implements Runnable {
return new ArrayList<>(Arrays.asList(token1, token2, token3, token4));
}
public static String getHostIpAddress() {
return hostIpAddress;
}
public static Set<Mark> getMarks() {
return Collections.unmodifiableSet(marks);
}
@@ -262,10 +240,6 @@ public class GameState implements Runnable {
if (currentStage == GameStages.PRE_RACE || currentStage == GameStages.RACING) {
update();
}
if (currentStage == GameStages.RACING) {
update();
}
}
}
@@ -290,6 +264,12 @@ public class GameState implements Runnable {
case DOWNWIND:
playerYacht.turnDownwind();
break;
case CONTINUOUSLY_TURNING:
playerYacht.setContinuouslyTurning(true);
break;
case DEFAULT_TURNING:
playerYacht.setContinuouslyTurning(false);
break;
}
}
@@ -342,6 +322,7 @@ public class GameState implements Runnable {
if (yacht.getPowerUp() != null) {
if (System.currentTimeMillis() - yacht.getPowerUpStartTime() > POWERUP_TIMEOUT_MS) {
yacht.powerDown();
sendServerMessage(yacht.getSourceId(), yacht.getBoatName() + "'s power-up token expired");
logger.debug("Yacht: " + yacht.getShortName() + " powered down!");
}
}
@@ -452,10 +433,12 @@ public class GameState implements Runnable {
//Token Collision
Token collidedToken = checkTokenPickUp(serverYacht);
if (collidedToken != null) {
sendServerMessage(serverYacht.getSourceId(), serverYacht.getBoatName() + " has picked speed-up token");
tokensInPlay.remove(collidedToken);
serverYacht.powerUp(collidedToken.getTokenType());
logger.debug("Yacht: " + serverYacht.getShortName() + " got powerup " + collidedToken
.getTokenType());
System.out.println("AGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGG");
notifyMessageListeners(MessageFactory.getRaceXML());
notifyMessageListeners(
new YachtEventCodeMessage(serverYacht.getSourceId(), YachtEventType.TOKEN));
@@ -466,7 +449,7 @@ public class GameState implements Runnable {
private void updateVelocity(ServerYacht yacht) {
Double trueWindAngle = Math.abs(windDirection - yacht.getHeading());
Double boatSpeedInKnots = PolarTable.getBoatSpeed(getWindSpeedKnots(), trueWindAngle);
Double maxBoatSpeed = GeoUtility.knotsToMMS(boatSpeedInKnots) * speedMultiplier;
Double maxBoatSpeed = GeoUtility.knotsToMMS(boatSpeedInKnots) * speedMultiplier * yacht.getMaxSpeedMultiplier();
if (yacht.getPowerUp() != null) {
if (yacht.getPowerUp().equals(TokenType.BOOST)) {
// TODO: 11/09/17 wmu16 CHANGE THIS TO MAGIC NUMBER
@@ -478,17 +461,17 @@ public class GameState implements Runnable {
// TODO: 15/08/17 remove magic numbers from these equations.
if (yacht.getSailIn()) {
if (currentVelocity < maxBoatSpeed - 500) {
yacht.changeVelocity(maxBoatSpeed / 100);
yacht.changeVelocity((maxBoatSpeed / 100) * yacht.getAccelerationMultiplier());
} else if (currentVelocity > maxBoatSpeed + 500) {
yacht.changeVelocity(-currentVelocity / 200);
yacht.changeVelocity((-currentVelocity / 200) * yacht.getAccelerationMultiplier());
} else {
yacht.setCurrentVelocity(maxBoatSpeed);
yacht.setCurrentVelocity((maxBoatSpeed) * yacht.getAccelerationMultiplier());
}
} else {
if (currentVelocity > 3000) {
yacht.changeVelocity(-currentVelocity / 200);
yacht.changeVelocity((-currentVelocity / 200) * yacht.getAccelerationMultiplier());
} else if (currentVelocity > 100) {
yacht.changeVelocity(-currentVelocity / 50);
yacht.changeVelocity((-currentVelocity / 50) * yacht.getAccelerationMultiplier());
} else if (currentVelocity <= 100) {
yacht.setCurrentVelocity(0d);
}
@@ -550,6 +533,9 @@ public class GameState implements Runnable {
}
if (hasProgressed) {
if (currentMarkSeqID != 0 && !markOrder.isLastMark(currentMarkSeqID)) {
sendServerMessage(yacht.getSourceId(), yacht.getBoatName() + " passed leg " + yacht.getLegNumber());
}
yacht.incrementLegNumber();
sendMarkRoundingMessage(yacht);
logMarkRounding(yacht);
@@ -584,6 +570,7 @@ public class GameState implements Runnable {
if (crossedLine == 2 && isClockwiseCross || crossedLine == 1 && !isClockwiseCross) {
yacht.setClosestCurrentMark(mark1);
yacht.setBoatStatus(BoatStatus.RACING);
sendServerMessage(yacht.getSourceId(), yacht.getBoatName() + " passed start line");
return true;
}
}
@@ -687,6 +674,7 @@ public class GameState implements Runnable {
if (crossedLine == 1 && isClockwiseCross || crossedLine == 2 && !isClockwiseCross) {
yacht.setClosestCurrentMark(mark1);
yacht.setBoatStatus(BoatStatus.FINISHED);
sendServerMessage(yacht.getSourceId(), yacht.getBoatName() + " passed finish line");
return true;
}
}
@@ -714,6 +702,9 @@ public class GameState implements Runnable {
int blue = customizeData[2] & 0xFF;
Color yachtColor = Color.rgb(red, green, blue);
playerYacht.setBoatColor(yachtColor);
} else if (requestType.equals(CustomizeRequestType.SHAPE)) {
String type = new String(customizeData);
playerYacht.setBoatType(BoatMeshType.valueOf(type));
}
}
@@ -792,28 +783,30 @@ public class GameState implements Runnable {
}
public static void sendServerMessage(Integer messageType, String message) {
notifyMessageListeners(new ChatterMessage(
messageType, "SERVER: " + message
));
}
public static void processChatter(ChatterMessage chatterMessage, boolean isHost) {
String chatterText = chatterMessage.getMessage();
String[] words = chatterText.split("\\s+");
if (words.length > 2 && isHost) {
switch (words[2].trim()) {
case ">speed":
case "/speed":
try {
setSpeedMultiplier(Double.valueOf(words[3]));
notifyMessageListeners(new ChatterMessage(
chatterMessage.getMessage_type(),
"SERVER: Speed modifier set to x" + words[3]
));
sendServerMessage(chatterMessage.getMessage_type(),
"Speed modifier set to x" + words[3]);
} catch (Exception e) {
Logger logger = LoggerFactory.getLogger(GameState.class);
logger.error("cannot parse >speed value");
}
return;
case ">finish":
notifyMessageListeners(new ChatterMessage(
chatterMessage.getMessage_type(),
"SERVER: Game will now finish"
));
case "/finish":
sendServerMessage(chatterMessage.getMessage_type(),
"Game will now finish");
endRace();
return;
}
@@ -1,30 +1,23 @@
package seng302.gameServer;
import java.io.IOException;
import java.io.StringReader;
import java.net.ServerSocket;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Random;
import java.util.Timer;
import java.util.TimerTask;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import seng302.gameServer.messages.Message;
import seng302.model.GeoPoint;
import seng302.model.Player;
import seng302.model.PolarTable;
import seng302.model.ServerYacht;
import seng302.model.mark.CompoundMark;
import seng302.model.stream.xml.parser.RaceXMLData;
import seng302.model.stream.xml.parser.RegattaXMLData;
import seng302.utilities.GeoUtility;
import seng302.utilities.XMLGenerator;
import seng302.utilities.XMLParser;
/**
* A class describing the overall server, which creates and collects server threads for each client
@@ -45,29 +38,13 @@ public class MainServerThread implements Runnable, ClientConnectionDelegate {
private Thread thread;
private ServerSocket serverSocket = null;
private ArrayList<ServerToClientThread> serverToClientThreads = new ArrayList<>();;
private ArrayList<ServerToClientThread> serverToClientThreads = new ArrayList<>();
private static Integer capacity;
private RaceXMLData raceXMLData;
private RegattaXMLData regattaXMLData;
private boolean serverStarted = false;
private void startAdvertisingServer() {
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
DocumentBuilder db;
Document doc;
XMLGenerator generator = new XMLGenerator();
try {
db = dbf.newDocumentBuilder();
String regatta = generator.getRegattaAsXml();
StringReader stringReader = new StringReader(regatta);
InputSource is = new InputSource(stringReader);
doc = db.parse(is);
} catch (ParserConfigurationException | IOException | SAXException e) {
logger.warn("Couldn't load race regatta");
return;
}
RegattaXMLData regattaXMLData = XMLParser.parseRegatta(doc);
Integer capacity = GameState.getCapacity();
Integer numPlayers = GameState.getNumberOfPlayers();
Integer spacesLeft = capacity - numPlayers;
@@ -79,33 +56,42 @@ public class MainServerThread implements Runnable, ClientConnectionDelegate {
// Start advertising server
try {
ServerAdvertiser.getInstance().setMapName(regattaXMLData.getCourseName()).setCapacity(capacity).setNumberOfPlayers(numPlayers);
ServerAdvertiser.getInstance().registerGame(PORT, regattaXMLData.getRegattaName());
ServerAdvertiser.getInstance()
.setMapName(regattaXMLData.getCourseName())
.setCapacity(capacity)
.setNumberOfPlayers(numPlayers)
.registerGame(PORT, regattaXMLData.getRegattaName());
} catch (IOException e) {
logger.warn("Could not register server");
}
}
public MainServerThread() {
new GameState("localhost");
new GameState();
try {
serverSocket = new ServerSocket(PORT);
} catch (IOException e) {
logger.trace("IO error in server thread handler upon trying to make new server socket",
0);
}
startAdvertisingServer();
PolarTable.parsePolarFile(getClass().getResourceAsStream("/config/acc_polars.csv"));
GameState.addMessageEventListener(this::broadcastMessage);
terminated = false;
thread = new Thread(this, "MainServer");
startUpdatingWind();
startSpawningTokens();
thread.start();
}
private void startServer() {
MessageFactory.updateXMLGenerator(raceXMLData, regattaXMLData);
GameState.setRace(raceXMLData);
MessageFactory.updateBoats(new ArrayList<>(GameState.getYachts().values()));
startAdvertisingServer();
PolarTable
.parsePolarFile(getClass().getResourceAsStream("/server_config/acc_polars.csv"));
GameState.addMessageEventListener(this::broadcastMessage);
startUpdatingWind();
startSpawningTokens();
System.out.println("SAAAANNNNNNNNNDDDDDDDDDDDDDDDDDDDDDDDDDDdd");
sendSetupMessages();
}
public void run() {
@@ -118,6 +104,7 @@ public class MainServerThread implements Runnable, ClientConnectionDelegate {
for (ServerToClientThread stc : serverToClientThreads) {
if (!stc.isSocketOpen()) {
GameState.getYachts().remove(stc.getSourceId());
System.out.println("AAAAAAAFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF");
sendSetupMessages();
try {
stc.getSocket().close();
@@ -133,9 +120,9 @@ public class MainServerThread implements Runnable, ClientConnectionDelegate {
} catch (InterruptedException e) {
logger.trace("Interrupted exception in Main Server Thread thread sleep", 1);
}
if (GameState.getCurrentStage() == GameStages.LOBBYING && GameState
.getCustomizationFlag()) {
// TODO: 16/08/17 ajm412: This can probably be done in a nicer way via those fancy functional interfaces.
if (GameState.getCurrentStage() == GameStages.LOBBYING && GameState.getCustomizationFlag()) {
MessageFactory.updateBoats(new ArrayList<>(GameState.getYachts().values()));
System.out.println("gfdgfdgfdg");
sendSetupMessages();
GameState.resetCustomizationFlag();
}
@@ -196,21 +183,21 @@ public class MainServerThread implements Runnable, ClientConnectionDelegate {
if (Math.floorMod(random.nextInt(), 2) == 0){
direction += random.nextInt(4);
windSpeed += random.nextInt(20) + 50;
windSpeed += random.nextInt(20) + 459;
}
else{
direction -= random.nextInt(4);
windSpeed -= random.nextInt(20) + 50;
windSpeed -= random.nextInt(20) + 459;
}
direction = Math.floorMod(direction, 360);
if (windSpeed > MAX_WIND_SPEED){
windSpeed -= random.nextInt(1000);
windSpeed -= random.nextInt(500);
}
if (windSpeed <= MIN_WIND_SPEED){
windSpeed += random.nextInt(1000);
windSpeed += random.nextInt(500);
}
GameState.setWindSpeed(Double.valueOf(windSpeed));
@@ -255,11 +242,34 @@ public class MainServerThread implements Runnable, ClientConnectionDelegate {
logger.debug("Player Connected From " + serverToClientThread.getThread().getName(), 0);
if (serverToClientThreads.size() == 0) { //Sets first client as host.
serverToClientThread.setAsHost();
serverToClientThread.raceXMLProperty().addListener((obs, oldVal, race) -> {
if (race != null) {
raceXMLData = race;
}
if (regattaXMLData != null) {
startServer();
}
});
serverToClientThread.regattaXMLProperty().addListener((obs, oldVal, regatta) -> {
if (regatta != null) {
regattaXMLData = regatta;
}
if (raceXMLData != null) {
startServer();
}
});
} else {
MessageFactory.updateBoats(new ArrayList<>(GameState.getYachts().values()));
for (ServerYacht serverYacht : GameState.getYachts().values()) {
System.out.println("Connecterino" + serverYacht);
}
serverToClientThread.addConnectionListener(() -> {
System.out.println("LUSTENER");
sendSetupMessages();
});
}
serverToClientThreads.add(serverToClientThread);
serverToClientThread.addConnectionListener(this::sendSetupMessages);
serverToClientThread.addDisconnectListener(this::clientDisconnected);
try {
ServerAdvertiser.getInstance().setNumberOfPlayers(GameState.getNumberOfPlayers());
} catch (IOException e) {
@@ -318,9 +328,9 @@ public class MainServerThread implements Runnable, ClientConnectionDelegate {
}, 0, 500);
if (GameState.getCurrentStage() == GameStages.LOBBYING) {
sendSetupMessages();
}
// if (GameState.getCurrentStage() == GameStages.LOBBYING) {
// sendSetupMessages();
// }
}
public void terminate() {
@@ -331,39 +341,166 @@ public class MainServerThread implements Runnable, ClientConnectionDelegate {
* Initialise boats to specific spaced out geopoints behind starting line.
*/
private void initialiseBoatPositions() {
CompoundMark cm = GameState.getMarkOrder().getMarkOrder().get(0);
GeoPoint startMark1 = cm.getSubMark(1);
GeoPoint startMark2 = cm.getSubMark(2);
// CompoundMark cm = GameState.getMarkOrder().getMarkOrder().get(0);
// GeoPoint startMark1 = cm.getSubMark(1);
// GeoPoint startMark2 = cm.getSubMark(2);
//
// // Calculating midpoint
// Double perpendicularAngle = GeoUtility.getBearing(startMark1, startMark2);
// Double length = GeoUtility.getDistance(startMark1, startMark2);
// GeoPoint midpoint = GeoUtility.getGeoCoordinate(startMark1, perpendicularAngle, length / 2);
//
// // Setting each boats position side by side
// final double SEPARATION = 50.0; // distance apart in meters
//
// int boatIndex = 0;
// for (ServerYacht yacht : GameState.getYachts().values()) {
// int distanceApart = boatIndex / 2;
//
// if (boatIndex % 2 == 1 && boatIndex != 0) {
// distanceApart++;
// distanceApart *= -1;
// }
//
// GeoPoint spawnMark = GeoUtility
// .getGeoCoordinate(midpoint, perpendicularAngle, distanceApart * SEPARATION);
//
// if (yacht.getHeading() < perpendicularAngle) {
// spawnMark = GeoUtility
// .getGeoCoordinate(spawnMark, perpendicularAngle + 90, SEPARATION);
// } else {
// spawnMark = GeoUtility
// .getGeoCoordinate(spawnMark, perpendicularAngle + 270, SEPARATION);
// }
//
// yacht.setLocation(spawnMark);
// boatIndex++;
// }
// Calculating midpoint
Double perpendicularAngle = GeoUtility.getBearing(startMark1, startMark2);
Double length = GeoUtility.getDistance(startMark1, startMark2);
GeoPoint midpoint = GeoUtility.getGeoCoordinate(startMark1, perpendicularAngle, length / 2);
// final double SEPARATION = 50.0; // distance apart in meters
//
// //Reverse of the angle from start to first mark
// double angleToFirstMark = 360 - GeoUtility.getBearing(
// GameState.getMarkOrder().getMarkOrder().get(0).getMidPoint(),
// GameState.getMarkOrder().getMarkOrder().get(1).getMidPoint()
// );
//
// //Length of start line
// double startLineLength = GeoUtility.getDistance(
// GameState.getMarkOrder().getMarkOrder().get(0).getSubMark(1),
// GameState.getMarkOrder().getMarkOrder().get(0).getSubMark(2)
// );
//
// //Angle of start line
// double startMarkToMarkAngle = GeoUtility.getBearing(
// GameState.getMarkOrder().getMarkOrder().get(0).getSubMark(1),
// GameState.getMarkOrder().getMarkOrder().get(0).getSubMark(2)
// );
//
// //How many yachts can fit along the start line
// int spacesAlongLine = (int) Math.round(startLineLength / SEPARATION);
// //The free space left by the boats.
// double buffer = (startLineLength % SEPARATION) / 2;
//
// //Randomize starting order.
// List<ServerYacht> serverYachtList = new ArrayList<>(GameState.getYachts().values());
// Collections.shuffle(serverYachtList);
//
// //set the starting point away from start line.
// GeoPoint startingPoint = GeoUtility.getGeoCoordinate(
// GameState.getMarkOrder().getMarkOrder().get(0).getSubMark(1),
// angleToFirstMark, SEPARATION
// );
//
// //Move it along the start line
// startingPoint = GeoUtility.getGeoCoordinate(
// startingPoint, startMarkToMarkAngle, buffer
// );
//
// int yachtCount = 0;
// int repeats = 0;
//
// GeoPoint yachtLocation;
//
// for (ServerYacht serverYacht : serverYachtList) {
//
// //Move away from start line
// yachtLocation = GeoUtility.getGeoCoordinate(
// startingPoint, angleToFirstMark,repeats * SEPARATION
// );
// //Move along start line
// yachtLocation = GeoUtility.getGeoCoordinate(
// yachtLocation, startMarkToMarkAngle, yachtCount * SEPARATION
// );
// serverYacht.setLocation(yachtLocation);
// serverYacht.setHeading(GeoUtility.getBearing(
// yachtLocation, GameState.getMarkOrder().getMarkOrder().get(1).getMidPoint()
// ));
// //Set location for next yacht
// yachtCount++;
// if (yachtCount > spacesAlongLine) {
// yachtCount = 0;
// repeats++;
// }
// }
// Setting each boats position side by side
double DISTANCE_FACTOR = 50.0; // distance apart in meters
int boatIndex = 0;
for (ServerYacht yacht : GameState.getYachts().values()) {
int distanceApart = boatIndex / 2;
final double DISTANCE_TO_START = 75d;
final double YACHT_SEPARATION = 20d;
if (boatIndex % 2 == 1 && boatIndex != 0) {
distanceApart++;
distanceApart *= -1;
//Length of start line
double startLineLength = GeoUtility.getDistance(
GameState.getMarkOrder().getMarkOrder().get(0).getSubMark(1),
GameState.getMarkOrder().getMarkOrder().get(0).getSubMark(2)
);
//How many yachts can fit along the start line
int spacesAlongLine = (int) Math.round(startLineLength / YACHT_SEPARATION);
//Angle of start line
double startMarkToMarkAngle = GeoUtility.getBearing(
GameState.getMarkOrder().getMarkOrder().get(0).getSubMark(1),
GameState.getMarkOrder().getMarkOrder().get(0).getSubMark(2)
);
//angle from first mark to the start
double angleToStart = GeoUtility.getBearing(
GameState.getMarkOrder().getMarkOrder().get(1).getMidPoint(),
GameState.getMarkOrder().getMarkOrder().get(0).getMidPoint()
);
double angleFromStart = GeoUtility.getBearing(
GameState.getMarkOrder().getMarkOrder().get(0).getMidPoint(),
GameState.getMarkOrder().getMarkOrder().get(1).getMidPoint()
);
GeoPoint startingPoint = GeoUtility.getGeoCoordinate(
GameState.getMarkOrder().getMarkOrder().get(0).getMidPoint(),
angleToStart, DISTANCE_TO_START
);
List<ServerYacht> randomisedYachts = new ArrayList<>(GameState.getYachts().values());
Collections.shuffle(randomisedYachts);
while (randomisedYachts.size() > 0) {
int numYachtsInLine = spacesAlongLine > randomisedYachts.size() ? randomisedYachts.size() : spacesAlongLine;
double yachtSpace = numYachtsInLine * YACHT_SEPARATION / 2;
GeoPoint firstYachtPoint = GeoUtility.getGeoCoordinate(
startingPoint, startMarkToMarkAngle + 180, yachtSpace
);
for (int i=0; i<numYachtsInLine; i++){
randomisedYachts.get(0).setHeading(angleFromStart);
randomisedYachts.get(0).setLocation(firstYachtPoint);
firstYachtPoint = GeoUtility.getGeoCoordinate(
firstYachtPoint, startMarkToMarkAngle, yachtSpace
);
randomisedYachts.remove(0);
}
GeoPoint spawnMark = GeoUtility
.getGeoCoordinate(midpoint, perpendicularAngle, distanceApart * DISTANCE_FACTOR);
if (yacht.getHeading() < perpendicularAngle) {
spawnMark = GeoUtility
.getGeoCoordinate(spawnMark, perpendicularAngle + 90, DISTANCE_FACTOR);
} else {
spawnMark = GeoUtility
.getGeoCoordinate(spawnMark, perpendicularAngle + 270, DISTANCE_FACTOR);
}
yacht.setLocation(spawnMark);
boatIndex++;
startingPoint = GeoUtility.getGeoCoordinate(
startingPoint, angleToStart, DISTANCE_TO_START
);
}
}
}
@@ -15,6 +15,8 @@ import seng302.model.Player;
import seng302.model.ServerYacht;
import seng302.model.stream.xml.generator.RaceXMLTemplate;
import seng302.model.stream.xml.generator.RegattaXMLTemplate;
import seng302.model.stream.xml.parser.RaceXMLData;
import seng302.model.stream.xml.parser.RegattaXMLData;
import seng302.model.token.Token;
import seng302.utilities.XMLGenerator;
@@ -35,6 +37,51 @@ Ideally this class would be created with an instance of the GameState (I tried i
public class MessageFactory {
private static XMLGenerator xmlGenerator = new XMLGenerator();
private static XMLMessage race;
private static XMLMessage regatta;
private static XMLMessage boats;
public static void updateXMLGenerator(RaceXMLData race, RegattaXMLData regatta) {
xmlGenerator.setRegattaTemplate(
new RegattaXMLTemplate(
regatta.getRegattaName(),
regatta.getCourseName(),
regatta.getCentralLat(),
regatta.getCentralLng()
)
);
xmlGenerator.setRaceTemplate(
new RaceXMLTemplate(
new ArrayList<>(),
new ArrayList<>(),
race.getMarkSequence(),
race.getCourseLimit(),
new ArrayList<>(race.getCompoundMarks().values()),
GameState.getCapacity(), true
)
);
String xmlStr = xmlGenerator.getRaceAsXml();
MessageFactory.race = new XMLMessage(xmlStr, XMLMessageSubType.RACE, xmlStr.length());
xmlStr = xmlGenerator.getRegattaAsXml();
MessageFactory.regatta = new XMLMessage(xmlStr, XMLMessageSubType.REGATTA, xmlStr.length());
xmlStr = xmlGenerator.getBoatsAsXml();
MessageFactory.boats = new XMLMessage(xmlStr, XMLMessageSubType.BOAT, xmlStr.length());
}
public static void updateBoats(List<ServerYacht> yachts) {
for (ServerYacht serverYacht : yachts) {
System.out.println(serverYacht);
}
xmlGenerator.getRace().setBoats(yachts);
String xmlStr = xmlGenerator.getBoatsAsXml();
MessageFactory.boats = new XMLMessage(xmlStr, XMLMessageSubType.BOAT, xmlStr.length());
}
public static void updateTokens(List<Token> tokens) {
xmlGenerator.getRace().setTokens(tokens);
String xmlStr = xmlGenerator.getRaceAsXml();
MessageFactory.race = new XMLMessage(xmlStr, XMLMessageSubType.RACE, xmlStr.length());
}
public static RaceStartStatusMessage getRaceStartStatusMessage() {
@@ -95,37 +142,14 @@ public class MessageFactory {
}
public static XMLMessage getRaceXML() {
List<ServerYacht> yachts = new ArrayList<>(GameState.getYachts().values());
List<Token> tokens = GameState.getTokensInPlay();
RaceXMLTemplate raceXMLTemplate = new RaceXMLTemplate(yachts, tokens);
xmlGenerator.setRaceTemplate(raceXMLTemplate);
XMLMessage raceXMLMessage = new XMLMessage(
xmlGenerator.getRaceAsXml(),
XMLMessageSubType.RACE,
xmlGenerator.getRaceAsXml().length());
return raceXMLMessage;
return race;
}
public static XMLMessage getRegattaXML() {
//@TODO calculate lat/lng values
return new XMLMessage(
xmlGenerator.getRegattaAsXml(),
XMLMessageSubType.REGATTA,
xmlGenerator.getRegattaAsXml().length());
return regatta;
}
public static XMLMessage getBoatXML() {
List<ServerYacht> yachts = new ArrayList<>(GameState.getYachts().values());
List<Token> tokens = GameState.getTokensInPlay();
RaceXMLTemplate raceXMLTemplate = new RaceXMLTemplate(yachts, tokens);
xmlGenerator.setRaceTemplate(raceXMLTemplate);
return new XMLMessage(
xmlGenerator.getBoatsAsXml(),
XMLMessageSubType.BOAT,
xmlGenerator.getBoatsAsXml().length());
return boats;
}
}
@@ -1,23 +1,21 @@
package seng302.gameServer;
import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.Socket;
import java.net.SocketException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.ThreadLocalRandom;
import java.util.stream.Collectors;
import java.util.zip.CRC32;
import java.util.zip.Checksum;
import javafx.beans.property.SimpleObjectProperty;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;
import seng302.gameServer.messages.BoatAction;
import seng302.gameServer.messages.ChatterMessage;
import seng302.gameServer.messages.ClientType;
@@ -25,14 +23,16 @@ import seng302.gameServer.messages.CustomizeRequestType;
import seng302.gameServer.messages.Message;
import seng302.gameServer.messages.RegistrationResponseMessage;
import seng302.gameServer.messages.RegistrationResponseStatus;
import seng302.gameServer.messages.XMLMessage;
import seng302.gameServer.messages.XMLMessageSubType;
import seng302.model.Player;
import seng302.model.ServerYacht;
import seng302.model.stream.packets.PacketType;
import seng302.model.stream.packets.StreamPacket;
import seng302.model.stream.xml.generator.RaceXMLTemplate;
import seng302.model.stream.xml.parser.RaceXMLData;
import seng302.model.stream.xml.parser.RegattaXMLData;
import seng302.utilities.StreamParser;
import seng302.utilities.XMLGenerator;
import seng302.visualiser.fxObjects.assets_3D.BoatMeshType;
import seng302.utilities.XMLParser;
/**
* A class describing a single connection to a Client for the purposes of sending and receiving on
@@ -80,6 +80,9 @@ public class ServerToClientThread implements Runnable {
private ServerYacht yacht;
private Player player;
private SimpleObjectProperty<RaceXMLData> raceXMLProperty = new SimpleObjectProperty<>();
private SimpleObjectProperty<RegattaXMLData> regattaXMLProperty = new SimpleObjectProperty<>();
public ServerToClientThread(Socket socket) {
this.socket = socket;
seqNo = 0;
@@ -100,37 +103,16 @@ public class ServerToClientThread implements Runnable {
}
private void setUpPlayer(){
BufferedReader fn;
String fName = "";
BufferedReader ln;
String fName = "Player" + GameState.getNumberOfPlayers().toString();
String lName = "";
fn = new BufferedReader(
new InputStreamReader(
ServerToClientThread.class.getResourceAsStream(
"/server_config/CSV_Database_of_First_Names.csv"
)
)
);
List<String> all = fn.lines().collect(Collectors.toList());
fName = all.get(ThreadLocalRandom.current().nextInt(0, all.size()));
ln = new BufferedReader(
new InputStreamReader(
ServerToClientThread.class.getResourceAsStream(
"/server_config/CSV_Database_of_Last_Names.csv"
)
)
);
all = ln.lines().collect(Collectors.toList());
lName = all.get(ThreadLocalRandom.current().nextInt(0, all.size()));
ServerYacht yacht = new ServerYacht(
"Yacht", sourceId, sourceId.toString(), fName, fName + " " + lName, "NZ"
BoatMeshType.DINGHY, sourceId, sourceId.toString(), fName, fName + " " + lName, "NZ"
);
System.out.println(yacht);
player = new Player(socket, yacht);
GameState.addYacht(sourceId, yacht);
GameState.addPlayer(player);
System.out.println(GameState.getYachts().size());
}
private void completeRegistration(ClientType clientType) throws IOException {
@@ -154,8 +136,9 @@ public class ServerToClientThread implements Runnable {
this.sourceId = sourceId;
isRegistered = true;
os.write(responseMessage.getBuffer());
System.out.println("MAKING A PLAYER");
setUpPlayer();
System.out.println("DONE MAKING A PLAYER");
for (ConnectionListener listener : connectionListeners) {
listener.notifyConnection();
@@ -185,37 +168,51 @@ public class ServerToClientThread implements Runnable {
long computedCrc = checksum.getValue();
long packetCrc = Message.bytesToLong(getBytes(4));
if (computedCrc == packetCrc) {
StreamPacket packet = new StreamPacket(type, payloadLength, timeStamp, payload);
switch (PacketType.assignPacketType(type, payload)) {
case BOAT_ACTION:
BoatAction actionType = ServerPacketParser
.extractBoatAction(
new StreamPacket(type, payloadLength, timeStamp, payload));
BoatAction actionType = ServerPacketParser.extractBoatAction(packet);
GameState.updateBoat(sourceId, actionType);
break;
case RACE_REGISTRATION_REQUEST:
ClientType requestedType = ServerPacketParser.extractClientType(
new StreamPacket(type, payloadLength, timeStamp, payload));
ClientType requestedType = ServerPacketParser
.extractClientType(packet);
completeRegistration(requestedType);
break;
case CHATTER_TEXT:
ChatterMessage chatterMessage = ServerPacketParser
.extractChatterText(
new StreamPacket(type, payloadLength, timeStamp, payload));
.extractChatterText(packet);
GameState.processChatter(chatterMessage, isHost);
break;
case RACE_CUSTOMIZATION_REQUEST:
Long sourceID = Message
.bytesToLong(Arrays.copyOfRange(payload, 0, 3));
Long sourceID = Message.bytesToLong(
Arrays.copyOfRange(payload, 0, 3)
);
CustomizeRequestType requestType = ServerPacketParser
.extractCustomizationType(
new StreamPacket(type, payloadLength, timeStamp, payload));
.extractCustomizationType(packet);
GameState.customizePlayer(sourceID, requestType,
Arrays.copyOfRange(payload, 6, payload.length));
Arrays.copyOfRange(payload, 6, payload.length)
);
GameState.setCustomizationFlag();
// TODO: 17/08/2017 ajm412: Send a response packet here, not really necessary until we do shapes.
break;
case RACE_XML:
Document document = StreamParser.extractXmlMessage(packet);
raceXMLProperty.set(
XMLParser.parseRace(document)
);
GameState.setMaxPlayers(XMLParser.getMaxPlayers(document));
break;
case REGATTA_XML:
regattaXMLProperty.set(
XMLParser.parseRegatta(
StreamParser.extractXmlMessage(packet)
)
);
break;
}
} else {
logger.warn("Packet has been dropped", 1);
@@ -232,25 +229,9 @@ public class ServerToClientThread implements Runnable {
}
public void sendSetupMessages() {
xmlGenerator = new XMLGenerator();
RaceXMLTemplate race = new RaceXMLTemplate(new ArrayList<>(GameState.getYachts().values()), new ArrayList<>());
xmlGenerator.setRaceTemplate(race);
System.out.println(xmlGenerator.getRegatta().getName());
XMLMessage xmlMessage;
xmlMessage = new XMLMessage(xmlGenerator.getRegattaAsXml(), XMLMessageSubType.REGATTA,
xmlGenerator.getRegattaAsXml().length());
sendMessage(xmlMessage);
xmlMessage = new XMLMessage(xmlGenerator.getBoatsAsXml(), XMLMessageSubType.BOAT,
xmlGenerator.getBoatsAsXml().length());
sendMessage(xmlMessage);
xmlMessage = new XMLMessage(xmlGenerator.getRaceAsXml(), XMLMessageSubType.RACE,
xmlGenerator.getRaceAsXml().length());
sendMessage(xmlMessage);
sendMessage(MessageFactory.getRegattaXML());
sendMessage(MessageFactory.getBoatXML());
sendMessage(MessageFactory.getRaceXML());
}
private void closeSocket() {
@@ -346,4 +327,12 @@ public class ServerToClientThread implements Runnable {
public void setAsHost() {
isHost = true;
}
public SimpleObjectProperty<RaceXMLData> raceXMLProperty() {
return raceXMLProperty;
}
public SimpleObjectProperty<RegattaXMLData> regattaXMLProperty() {
return regattaXMLProperty;
}
}
@@ -14,7 +14,9 @@ public enum BoatAction {
TACK_GYBE(4),
UPWIND(5),
DOWNWIND(6),
MAINTAIN_HEADING(7);
MAINTAIN_HEADING(7),
CONTINUOUSLY_TURNING(8),
DEFAULT_TURNING(9);
private final int type;
private static final Map<Integer, BoatAction> intToTypeMap = new HashMap<>();
@@ -5,19 +5,19 @@ package seng302.gameServer.messages;
*/
public class BoatActionMessage extends Message{
private final MessageType MESSAGE_TYPE = MessageType.BOAT_ACTION;
private final int MESSAGE_SIZE = 1;
private final int MESSAGE_SIZE = 5;
private BoatAction actionType;
public BoatActionMessage(BoatAction actionType) {
public BoatActionMessage(BoatAction actionType, int sourceId) {
this.actionType = actionType;
setHeader(new Header(MessageType.BOAT_ACTION, 0, (short) 1)); // the second variable is the source id
setHeader(new Header(MessageType.BOAT_ACTION, sourceId, (short) MESSAGE_SIZE)); // the second variable is the source id
allocateBuffer();
writeHeaderToBuffer();
// Write message fields
putInt(actionType.getValue(), 1);
putInt(sourceId, 4);
writeCRC();
rewind();
}
@Override
@@ -4,8 +4,8 @@ package seng302.gameServer.messages;
public class RegistrationRequestMessage extends Message {
private static int MESSAGE_LENGTH = 2;
public RegistrationRequestMessage(ClientType type){
setHeader(new Header(MessageType.REGISTRATION_REQUEST, 1, (short) getSize()));
public RegistrationRequestMessage(ClientType type, int clientID){
setHeader(new Header(MessageType.REGISTRATION_REQUEST, clientID, (short) getSize()));
allocateBuffer();
writeHeaderToBuffer();
+32 -5
View File
@@ -6,6 +6,7 @@ import java.util.ArrayList;
import java.util.List;
import java.util.Observable;
import java.util.Observer;
import java.util.Timer;
import javafx.beans.property.ReadOnlyDoubleProperty;
import javafx.beans.property.ReadOnlyDoubleWrapper;
import javafx.beans.property.ReadOnlyIntegerProperty;
@@ -15,6 +16,8 @@ import javafx.beans.property.ReadOnlyLongWrapper;
import javafx.scene.paint.Color;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import seng302.visualiser.fxObjects.assets_3D.BoatMeshType;
import seng302.visualiser.fxObjects.assets_3D.BoatObject;
/**
* Yacht class for the racing boat. <p> Class created to store more variables (eg. boat statuses)
@@ -37,7 +40,7 @@ public class ClientYacht extends Observable {
private Logger logger = LoggerFactory.getLogger(ClientYacht.class);
private String boatType;
private BoatMeshType boatType;
private Integer sourceId;
private String hullID; //matches HullNum in the XML spec.
private String shortName;
@@ -46,7 +49,7 @@ public class ClientYacht extends Observable {
private Integer position;
private Long estimateTimeAtFinish;
private Boolean sailIn = false;
private Boolean sailIn = true;
private Integer currentMarkSeqID = 0;
private Long markRoundTime;
private Long timeTillNext;
@@ -56,15 +59,20 @@ public class ClientYacht extends Observable {
private Integer boatStatus;
private Double currentVelocity;
Timer t;
private BoatObject boatObject;
private List<YachtLocationListener> locationListeners = new ArrayList<>();
private List<MarkRoundingListener> markRoundingListeners = new ArrayList<>();
private ReadOnlyDoubleWrapper velocityProperty = new ReadOnlyDoubleWrapper();
private ReadOnlyLongWrapper timeTillNextProperty = new ReadOnlyLongWrapper();
private ReadOnlyLongWrapper timeSinceLastMarkProperty = new ReadOnlyLongWrapper();
private ReadOnlyIntegerWrapper placingProperty = new ReadOnlyIntegerWrapper();
private ReadOnlyDoubleWrapper headingProperty = new ReadOnlyDoubleWrapper();
private Color colour;
public ClientYacht(String boatType, Integer sourceId, String hullID, String shortName,
public ClientYacht(BoatMeshType boatType, Integer sourceId, String hullID, String shortName,
String boatName, String country) {
this.boatType = boatType;
this.sourceId = sourceId;
@@ -74,6 +82,7 @@ public class ClientYacht extends Observable {
this.country = country;
this.location = new GeoPoint(57.670341, 11.826856);
this.heading = 120.0; //In degrees
this.headingProperty.set(this.heading);
this.currentVelocity = 0d;
this.boatStatus = 1;
this.colour = Color.rgb(0, 0, 0, 1.0);
@@ -88,7 +97,7 @@ public class ClientYacht extends Observable {
super.addObserver(o);
}
public String getBoatType() {
public BoatMeshType getBoatType() {
return boatType;
}
@@ -221,6 +230,7 @@ public class ClientYacht extends Observable {
public void setHeading(Double heading) {
this.heading = heading;
setHeadingProperty();
}
@Override
@@ -249,10 +259,10 @@ public class ClientYacht extends Observable {
this.colour = colour;
}
public void updateLocation(double lat, double lng, double heading, double velocity) {
setLocation(lat, lng);
this.heading = heading;
setHeadingProperty();
this.currentVelocity = velocity;
updateVelocityProperty(velocity);
for (YachtLocationListener yll : locationListeners) {
@@ -260,6 +270,10 @@ public class ClientYacht extends Observable {
}
}
private void setHeadingProperty() {
headingProperty.set(heading);
}
public void addLocationListener(YachtLocationListener listener) {
locationListeners.add(listener);
}
@@ -288,4 +302,17 @@ public class ClientYacht extends Observable {
public Double getCurrentVelocity() {
return currentVelocity;
}
public void setBoatObject(BoatObject newBoatObject) {
this.boatObject = newBoatObject;
}
public BoatObject getBoatObject() {
return this.boatObject;
}
public ReadOnlyDoubleWrapper getHeadingProperty() {
return headingProperty;
}
}
@@ -0,0 +1,87 @@
package seng302.model;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import javafx.scene.input.KeyCode;
public class GameKeyBind {
private static GameKeyBind instance;
private Map<KeyCode, KeyAction> keyToActionMap;
private Map<KeyAction, KeyCode> actionToKeyMap;
private Boolean continuouslyTurning;
private GameKeyBind() {
setToDefault();
}
public void setToDefault() {
actionToKeyMap = new HashMap<>();
keyToActionMap = new HashMap<>();
continuouslyTurning = false;
// default key bindings
ArrayList<KeyCode> keys = new ArrayList<>();
keys.add(KeyCode.Z);
keys.add(KeyCode.X);
keys.add(KeyCode.SPACE);
keys.add(KeyCode.SHIFT);
keys.add(KeyCode.ENTER);
keys.add(KeyCode.PAGE_UP);
keys.add(KeyCode.PAGE_DOWN);
keys.add(KeyCode.F1);
keys.add(KeyCode.D);
keys.add(KeyCode.A);
keys.add(KeyCode.W);
keys.add(KeyCode.S);
for (int i = 0; i < 12; i++) {
actionToKeyMap.put(KeyAction.getType(i + 1), keys.get(i));
keyToActionMap.put(keys.get(i), KeyAction.getType(i + 1));
}
}
public static GameKeyBind getInstance() {
if (instance == null) {
instance = new GameKeyBind();
}
return instance;
}
public KeyCode getKeyCode(KeyAction keyAction) {
return instance.actionToKeyMap.get(keyAction);
}
public KeyAction getKeyAction(KeyCode keyCode) {
return instance.keyToActionMap.get(keyCode);
}
/**
* Binds a key to a key action
*
* @return true if successfully bind
*/
public boolean bindKeyToAction(KeyCode keyCode, KeyAction keyAction) {
if (instance.keyToActionMap.containsKey(keyCode)) {
// if the key has been bound to other action, return false
return false;
} else {
instance.keyToActionMap.put(keyCode, keyAction); // add key -> action
KeyCode oldKeyCode = instance.actionToKeyMap
.get(keyAction); // get old key for the action
instance.keyToActionMap.remove(oldKeyCode); // remove the old key -> action
instance.actionToKeyMap
.replace(keyAction, keyCode); // replace the old key by the newer one
return true;
}
}
public void toggleTurningMode() {
continuouslyTurning = !continuouslyTurning;
}
public Boolean isContinuouslyTurning() {
return continuouslyTurning;
}
}
@@ -0,0 +1,40 @@
package seng302.model;
import java.util.HashMap;
import java.util.Map;
public enum KeyAction {
ZOOM_IN(1),
ZOOM_OUT(2),
VMG(3),
SAILS_STATE(4),
TACK_GYBE(5),
UPWIND(6),
DOWNWIND(7),
VIEW(8),
RIGHT(9),
LEFT(10),
FORWARD(11),
BACKWARD(12);
private final int type;
private static final Map<Integer, KeyAction> intToTypeMap = new HashMap<>();
static {
for (KeyAction type : KeyAction.values()) {
intToTypeMap.put(type.getValue(), type);
}
}
KeyAction(int type) {
this.type = type;
}
public static KeyAction getType(int value) {
return intToTypeMap.get(value);
}
public int getValue() {
return this.type;
}
}
+5
View File
@@ -15,4 +15,9 @@ public class Limit extends GeoPoint {
public Integer getSeqID() {
return seqID;
}
@Override
public String toString(){
return "Limit = {seqID=" + seqID + ", lat=" + getLat() + ", lng=" + getLng() + "}";
}
}
+15 -6
View File
@@ -3,11 +3,8 @@ package seng302.model;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.Collection;
import java.util.Comparator;
import java.util.List;
import java.util.Observable;
import java.util.TimeZone;
import javafx.beans.property.ReadOnlyDoubleProperty;
import javafx.beans.property.ReadOnlyDoubleWrapper;
@@ -34,8 +31,9 @@ public class RaceState {
private ReadOnlyDoubleWrapper windDirection = new ReadOnlyDoubleWrapper();
private long serverSystemTime;
private long expectedStartTime;
private boolean isRaceStarted = false;
private boolean raceRunning = false;
private boolean gunFired = false;
private boolean raceFinished = false;
long timeTillStart;
private ObservableList<ClientYacht> playerPositions;
private List<ClientYacht> collisions = new ArrayList<>();
@@ -50,7 +48,7 @@ public class RaceState {
this.windDirection.set(data.getWindDirection());
this.serverSystemTime = data.getCurrentTime();
this.expectedStartTime = data.getExpectedStartTime();
this.isRaceStarted = data.isRaceStarted();
this.raceRunning = data.isRaceStarted();
}
public void setTimeZone (TimeZone timeZone) {
@@ -95,9 +93,12 @@ public class RaceState {
}
public boolean isRaceStarted () {
return isRaceStarted;
return raceRunning;
}
public void setRaceStarted(Boolean value) {
this.raceRunning = value;
}
public void setBoats(Collection<ClientYacht> clientYachts) {
playerPositions.setAll(clientYachts);
}
@@ -125,4 +126,12 @@ public class RaceState {
public void removeCollisionListener(CollisionListener collisionListener) {
collisionListeners.remove(collisionListener);
}
public void setRaceFinished() {
raceFinished = true;
}
public Boolean getRaceFinished() {
return raceFinished;
}
}
@@ -0,0 +1,122 @@
package seng302.model;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import javafx.geometry.Point2D;
import seng302.utilities.GeoUtility;
/**
* Contains information on a scaled lat lon point for use with mapping geographical elements to a 2d plane.
*/
public class ScaledPoint extends GeoPoint {
public enum ScaleDirection {
HORIZONTAL,
VERTICAL
}
private double x, y, scaleFactor;
private ScaleDirection scaleDirection;
private ScaledPoint(double lat, double lng, double x, double y, double scaleFactor, ScaleDirection direction) {
super(lat, lng);
this.x = x;
this.y = y;
this.scaleFactor = scaleFactor;
this.scaleDirection = direction;
}
public double getX() {
return x;
}
public double getY() {
return y;
}
public double getScaleFactor() {
return scaleFactor;
}
public ScaleDirection getScaleDirection() {
return scaleDirection;
}
public Point2D findScaledXY(GeoPoint unscaled) {
return findScaledXY(unscaled.getLat(), unscaled.getLng());
}
public Point2D findScaledXY(double unscaledLat, double unscaledLon) {
double distanceFromReference;
double angleFromReference;
double xReference = this.getX();
double yReference = this.getY();
angleFromReference = GeoUtility.getBearingRad(
this, new GeoPoint(unscaledLat, unscaledLon)
);
distanceFromReference = GeoUtility.getDistance(
this, new GeoPoint(unscaledLat, unscaledLon)
);
if (angleFromReference >= 0 && angleFromReference <= Math.PI / 2) {
xReference += scaleFactor * Math.sin(angleFromReference) * distanceFromReference;
yReference -= scaleFactor * Math.cos(angleFromReference) * distanceFromReference;
} else if (angleFromReference >= 0) {
angleFromReference = angleFromReference - Math.PI / 2;
xReference += scaleFactor * Math.cos(angleFromReference) * distanceFromReference;
yReference += scaleFactor * Math.sin(angleFromReference) * distanceFromReference;
} else if (angleFromReference < 0 && angleFromReference >= -Math.PI / 2) {
angleFromReference = Math.abs(angleFromReference);
xReference -= scaleFactor * Math.sin(angleFromReference) * distanceFromReference;
yReference -= scaleFactor * Math.cos(angleFromReference) * distanceFromReference;
} else {
angleFromReference = Math.abs(angleFromReference) - Math.PI / 2;
xReference -= scaleFactor * Math.cos(angleFromReference) * distanceFromReference;
yReference += scaleFactor * Math.sin(angleFromReference) * distanceFromReference;
}
return new Point2D(xReference, yReference);
}
public static ScaledPoint makeScaledPoint(double width, double height,
List<? extends GeoPoint> points, boolean centered) {
double referencePointX, referencePointY, scaleFactor, lat, lng;
ScaleDirection scaleDirection;
points = new ArrayList<>(points);
points.sort(Comparator.comparingDouble(GeoPoint::getLat));
GeoPoint minLatPoint = points.get(0);
GeoPoint maxLatPoint = points.get(points.size() - 1);
points.sort(Comparator.comparingDouble(GeoPoint::getLng));
GeoPoint minLonPoint = points.get(0);
GeoPoint maxLonPoint = points.get(points.size() - 1);
referencePointX = centered ? 0 : width / 2;
referencePointY = centered ? 0 : height / 2;
lat = (maxLatPoint.getLat() - minLatPoint.getLat()) / 2 + minLatPoint.getLat();
lng = (maxLonPoint.getLng() - minLonPoint.getLng()) / 2 + minLonPoint.getLng();
GeoPoint ref = new GeoPoint(lat, lng);
double vertDistance = GeoUtility.getDistance(
ref, new GeoPoint(ref.getLat(), maxLonPoint.getLng())
) * 2.1;
double horiDistance = GeoUtility.getDistance(
ref, new GeoPoint(maxLatPoint.getLat(), ref.getLng())
) * 2.1;
double vertScale = height / vertDistance;
if (horiDistance * vertScale > width) {
scaleFactor = width / horiDistance;
scaleDirection = ScaleDirection.HORIZONTAL;
} else {
scaleFactor = vertScale;
scaleDirection = ScaleDirection.VERTICAL;
}
return new ScaledPoint(lat, lng, referencePointX, referencePointY, scaleFactor, scaleDirection);
}
}
+84 -48
View File
@@ -1,5 +1,6 @@
package seng302.model;
import java.util.HashMap;
import javafx.scene.paint.Color;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -8,10 +9,7 @@ import seng302.gameServer.messages.BoatStatus;
import seng302.model.mark.Mark;
import seng302.model.token.TokenType;
import seng302.utilities.GeoUtility;
import java.util.HashMap;
import java.util.Observable;
import java.util.Observer;
import seng302.visualiser.fxObjects.assets_3D.BoatMeshType;
/**
* Yacht class for the racing boat. <p> Class created to store more variables (eg. boat statuses)
@@ -20,12 +18,14 @@ import java.util.Observer;
*/
public class ServerYacht {
private Logger logger = LoggerFactory.getLogger(ClientYacht.class);
public static final Double TURN_STEP = 5.0;
private Logger logger = LoggerFactory.getLogger(ServerYacht.class);
//Boat info
private String boatType;
private BoatMeshType boatType;
private Double turnStep = 5.0;
private Double maxSpeedMultiplier = 1.0;
private Double turnStepMultiplier = 1.0;
private Double accelerationMultiplier = 1.0;
private Integer sourceId;
private String hullID; //matches HullNum in the XML spec.
private String shortName;
@@ -56,10 +56,12 @@ public class ServerYacht {
private TokenType powerUp;
private Long powerUpStartTime;
//turning mode
private Boolean continuouslyTurning;
public ServerYacht(String boatType, Integer sourceId, String hullID, String shortName,
public ServerYacht(BoatMeshType boatType, Integer sourceId, String hullID, String shortName,
String boatName, String country) {
this.boatType = boatType;
setBoatType(boatType);
this.boatStatus = BoatStatus.PRESTART;
this.sourceId = sourceId;
this.hullID = hullID;
@@ -80,6 +82,8 @@ public class ServerYacht {
this.hasEnteredRoundingZone = false;
this.hasPassedLine = false;
this.hasPassedThroughGate = false;
this.continuouslyTurning = false;
}
@@ -129,7 +133,7 @@ public class ServerYacht {
* @param amount the amount by which to adjust the boat heading.
*/
public void adjustHeading(Double amount) {
Double newVal = heading + amount;
Double newVal = heading + (amount * turnStepMultiplier);
lastHeading = heading;
heading = (double) Math.floorMod(newVal.longValue(), 360L);
}
@@ -152,11 +156,11 @@ public class ServerYacht {
/**
* Enables the boats auto pilot feature, which will move the boat towards a given heading.
*
* @param thisHeading The heading to move the boat towards.
* @param newHeading The heading to move the boat towards.
*/
private void setAutoPilot(Double thisHeading) {
private void setAutoPilot(Double newHeading) {
isAuto = true;
autoHeading = thisHeading;
autoHeading = newHeading;
}
/**
@@ -174,8 +178,9 @@ public class ServerYacht {
if (isAuto) {
turnTowardsHeading(autoHeading);
if (Math.abs(heading - autoHeading)
<= TURN_STEP) { //Cancel when within 1 turn step of target.
<= turnStep*1.5) {
isAuto = false;
setHeading(autoHeading);
}
}
}
@@ -187,44 +192,52 @@ public class ServerYacht {
public void turnUpwind() {
disableAutoPilot();
Double normalizedHeading = normalizeHeading();
if (normalizedHeading == 0) {
if (lastHeading < 180) {
adjustHeading(-TURN_STEP);
} else {
adjustHeading(TURN_STEP);
}
} else if (normalizedHeading == 180) {
if (lastHeading < 180) {
adjustHeading(TURN_STEP);
} else {
adjustHeading(-TURN_STEP);
}
} else if (normalizedHeading < 180) {
adjustHeading(-TURN_STEP);
if (continuouslyTurning) {
adjustHeading(turnStep);
} else {
adjustHeading(TURN_STEP);
if (normalizedHeading == 0) {
if (lastHeading < 180) {
adjustHeading(-turnStep);
} else {
adjustHeading(turnStep);
}
} else if (normalizedHeading == 180) {
if (lastHeading < 180) {
adjustHeading(turnStep);
} else {
adjustHeading(-turnStep);
}
} else if (normalizedHeading < 180) {
adjustHeading(-turnStep);
} else {
adjustHeading(turnStep);
}
}
}
public void turnDownwind() {
disableAutoPilot();
Double normalizedHeading = normalizeHeading();
if (normalizedHeading == 0) {
if (lastHeading < 180) {
adjustHeading(TURN_STEP);
} else {
adjustHeading(-TURN_STEP);
}
} else if (normalizedHeading == 180) {
if (lastHeading < 180) {
adjustHeading(-TURN_STEP);
} else {
adjustHeading(TURN_STEP);
}
} else if (normalizedHeading < 180) {
adjustHeading(TURN_STEP);
if (continuouslyTurning) {
adjustHeading(-turnStep);
} else {
adjustHeading(-TURN_STEP);
if (normalizedHeading == 0) {
if (lastHeading < 180) {
adjustHeading(turnStep);
} else {
adjustHeading(-turnStep);
}
} else if (normalizedHeading == 180) {
if (lastHeading < 180) {
adjustHeading(-turnStep);
} else {
adjustHeading(turnStep);
}
} else if (normalizedHeading < 180) {
adjustHeading(turnStep);
} else {
adjustHeading(-turnStep);
}
}
}
@@ -253,7 +266,7 @@ public class ServerYacht {
// Take optimal heading and turn into a boat heading rather than a wind heading.
optimalHeading =
optimalHeading + GameState.getWindDirection();
(optimalHeading + GameState.getWindDirection()) % 360;
setAutoPilot(optimalHeading);
}
@@ -268,9 +281,9 @@ public class ServerYacht {
private void turnTowardsHeading(Double newHeading) {
Double newVal = heading - newHeading;
if (Math.floorMod(newVal.longValue(), 360L) > 180) {
adjustHeading(TURN_STEP / 5);
adjustHeading(turnStep / 5);
} else {
adjustHeading(-TURN_STEP / 5);
adjustHeading(-turnStep / 5);
}
}
@@ -421,4 +434,27 @@ public class ServerYacht {
return boatColor;
}
public void setBoatType(BoatMeshType boatType) {
this.accelerationMultiplier = boatType.accelerationMultiplier;
this.maxSpeedMultiplier = boatType.maxSpeedMultiplier;
this.turnStepMultiplier = boatType.turnStep;
this.boatType = boatType;
}
public Double getMaxSpeedMultiplier() {
return maxSpeedMultiplier;
}
public Double getAccelerationMultiplier(){
return accelerationMultiplier;
}
public BoatMeshType getBoatType() {
return boatType;
}
public void setContinuouslyTurning(Boolean continuouslyTurning) {
this.continuouslyTurning = continuouslyTurning;
}
}
@@ -1,6 +1,6 @@
package seng302.model.mark;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import seng302.gameServer.messages.RoundingSide;
import seng302.model.GeoPoint;
@@ -10,13 +10,13 @@ public class CompoundMark {
private int compoundMarkId;
private String name;
private List<Mark> marks = new ArrayList<>();
private List<Mark> marks;
private GeoPoint midPoint;
public CompoundMark(int markID, String name, List<Mark> marks) {
this.compoundMarkId = markID;
this.name = name;
this.marks.addAll(marks);
this.marks = Collections.unmodifiableList(marks);
if (marks.size() > 1) {
this.midPoint = GeoUtility.getDirtyMidPoint(marks.get(0), marks.get(1));
} else {
@@ -32,4 +32,10 @@ public class Corner {
public Integer getZoneSize() {
return zoneSize;
}
@Override
public String toString() {
return "Corner = {seqID=" + seqID + ", compoundMarkID=" + compoundMarkID + ", rounding="
+ rounding +", zoneSize=" + zoneSize + "}";
}
}
+13 -84
View File
@@ -1,23 +1,12 @@
package seng302.model.mark;
import java.io.IOException;
import java.io.StringReader;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import seng302.gameServer.messages.RoundingSide;
import seng302.model.ServerYacht;
import seng302.model.stream.xml.generator.RaceXMLTemplate;
import seng302.model.stream.xml.parser.RaceXMLData;
import seng302.model.token.Token;
import seng302.utilities.XMLGenerator;
import seng302.utilities.XMLParser;
import java.util.*;
/**
* Class to hold the order of the marks in the race.
@@ -25,10 +14,17 @@ import java.util.*;
public class MarkOrder {
private List<CompoundMark> raceMarkOrder;
private Logger logger = LoggerFactory.getLogger(MarkOrder.class);
private Set<Mark> allMarks;
public MarkOrder(){
loadRaceProperties();
public MarkOrder(RaceXMLData raceXMLData){
raceMarkOrder = new ArrayList<>();
for (Corner corner : raceXMLData.getMarkSequence()){
CompoundMark compoundMark = raceXMLData.getCompoundMarks().get(corner.getCompoundMarkID());
compoundMark.setRoundingSide(
RoundingSide.getRoundingSide(corner.getRounding())
);
raceMarkOrder.add(compoundMark);
}
}
/**
@@ -40,7 +36,6 @@ public class MarkOrder {
logger.warn("Race order accessed but not instantiated");
return null;
}
return Collections.unmodifiableList(raceMarkOrder);
}
@@ -74,70 +69,4 @@ public class MarkOrder {
public CompoundMark getNextMark(Integer currentSeqID) throws IndexOutOfBoundsException {
return raceMarkOrder.get(currentSeqID + 1);
}
public Set<Mark> getAllMarks(){
return Collections.unmodifiableSet(allMarks);
}
/**
* Loads the race order from an XML string
* @param xml An AC35 RaceXML
* @return An ordered list of marks in the race
*/
private List<CompoundMark> loadRaceOrderFromXML(String xml) {
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
DocumentBuilder db;
Document doc;
allMarks = new HashSet<>();
try {
db = dbf.newDocumentBuilder();
doc = db.parse(new InputSource(new StringReader(xml)));
} catch (ParserConfigurationException | IOException | SAXException e) {
logger.error("Failed to read generated race XML");
return null;
}
RaceXMLData data = XMLParser.parseRace(doc);
if (data != null){
logger.debug("Loaded RaceXML for mark order");
List<Corner> corners = data.getMarkSequence();
Map<Integer, CompoundMark> marks = data.getCompoundMarks();
List<CompoundMark> course = new ArrayList<>();
for (Corner corner : corners){
CompoundMark compoundMark = marks.get(corner.getCompoundMarkID());
compoundMark.setRoundingSide(
RoundingSide.getRoundingSide(corner.getRounding())
);
course.add(compoundMark);
allMarks.addAll(compoundMark.getMarks());
}
return course;
}
return null;
}
/**
* Load the raceXML and mark order
*/
private void loadRaceProperties(){
XMLGenerator generator = new XMLGenerator();
// TODO: 29/08/17 wmu16 - This is terrible, having to make a template just to receive constant data
generator.setRaceTemplate(new RaceXMLTemplate(
new ArrayList<>(),
new ArrayList<>()));
String raceXML = generator.getRaceAsXml();
if (raceXML == null){
logger.error("Failed to generate raceXML (for race properties)");
return;
}
raceMarkOrder = loadRaceOrderFromXML(raceXML);
}
}
@@ -1,10 +1,12 @@
package seng302.model.stream.xml.generator;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import seng302.model.Limit;
import seng302.model.ServerYacht;
import seng302.model.mark.CompoundMark;
import seng302.model.mark.Corner;
import seng302.model.token.Token;
/**
@@ -15,11 +17,22 @@ public class RaceXMLTemplate {
private List<ServerYacht> yachts;
private LocalDateTime startTime;
private List<Token> tokens;
private List<Corner> roundings;
private List<Limit> courseLimit;
private List<CompoundMark> course;
private Integer maxPlayers;
private Boolean tokensEnabled;
public RaceXMLTemplate(List<ServerYacht> yachts, List<Token> tokens) {
public RaceXMLTemplate(List<ServerYacht> yachts, List<Token> tokens, List<Corner> roundings,
List<Limit> limit, List<CompoundMark> course, Integer maxPlayers, Boolean tokensEnabled) {
this.yachts = yachts;
this.tokens = tokens;
this.roundings = roundings;
this.courseLimit = limit;
this.course = course;
startTime = LocalDateTime.now();
this.maxPlayers = maxPlayers;
this.tokensEnabled = tokensEnabled;
}
/**
@@ -39,6 +52,18 @@ public class RaceXMLTemplate {
return Collections.unmodifiableList(tokens);
}
public List<CompoundMark> getCompoundMarks() {
return Collections.unmodifiableList(course);
}
public List<Limit> getCourseLimit() {
return Collections.unmodifiableList(courseLimit);
}
public List<Corner> getRoundings() {
return Collections.unmodifiableList(roundings);
}
/**
* Set the time until the race starts
* @param seconds The time in seconds until the race starts
@@ -54,4 +79,20 @@ public class RaceXMLTemplate {
public String getRaceStartTime(){
return startTime.toString();
}
public void setBoats(List<ServerYacht> boats) {
yachts = boats;
}
public void setTokens(List<Token> tokens) {
this.tokens = tokens;
}
public String getTokensEnabled() {
return tokensEnabled.toString();
}
public String getMaxPlayers() {
return maxPlayers.toString();
}
}
+15 -7
View File
@@ -14,8 +14,10 @@ public class Sounds {
private static MediaPlayer soundEffect;
private static MediaPlayer soundPlayer;
private static MediaPlayer hoverSoundPlayer;
private static MediaPlayer crashSoundPlayer;
private static boolean hoverInitialized = false;
private static boolean crashInitialized = false;
private static boolean musicMuted = false;
private static boolean soundEffectsMuted = false;
@@ -155,11 +157,17 @@ public class Sounds {
public static void playCrashSound() {
if (!soundEffectsMuted) {
Media crashSound = new Media(
Sounds.class.getClassLoader().getResource("sounds/Large-metal-door-slam.mp3")
.toString());
soundPlayer = new MediaPlayer(crashSound);
soundPlayer.play();
if (!crashInitialized) {
Media pickupSound = new Media(
Sounds.class.getClassLoader().getResource("sounds/Large-metal-door-slam.mp3")
.toString());
crashSoundPlayer = new MediaPlayer(pickupSound);
crashInitialized = true;
}
if (crashSoundPlayer != null) {
crashSoundPlayer.stop();
}
crashSoundPlayer.play();
}
}
@@ -176,10 +184,10 @@ public class Sounds {
public static void playHoverSound() {
if (!soundEffectsMuted) {
if (!hoverInitialized) {
Media crashSound = new Media(
Media hoverSound = new Media(
Sounds.class.getClassLoader().getResource("sounds/Error-sound-effect.mp3")
.toString());
hoverSoundPlayer = new MediaPlayer(crashSound);
hoverSoundPlayer = new MediaPlayer(hoverSound);
hoverInitialized = true;
}
hoverSoundPlayer.setVolume(0.5);
@@ -2,7 +2,6 @@ package seng302.utilities;
import java.io.IOException;
import java.io.StringReader;
import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@@ -15,8 +14,12 @@ import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import seng302.model.stream.packets.PacketType;
import seng302.model.stream.packets.StreamPacket;
import seng302.model.stream.parser.*;
import seng302.model.stream.parser.MarkRoundingData;
import seng302.model.stream.parser.PositionUpdateData;
import seng302.model.stream.parser.PositionUpdateData.DeviceType;
import seng302.model.stream.parser.RaceStartData;
import seng302.model.stream.parser.RaceStatusData;
import seng302.model.stream.parser.YachtEventData;
/**
* StreamParser is a utilities class for taking byte data, formatted according to the AC35 streaming
@@ -37,7 +40,6 @@ public class StreamParser {
return null;
}
long heartbeat = bytesToLong(packet.getPayload());
System.out.println("heartbeat = " + heartbeat);
return heartbeat;
}
@@ -134,7 +136,7 @@ public class StreamParser {
long messageLength = bytesToLong(Arrays.copyOfRange(payload, 12, 14));
String xmlMessage = new String(
(Arrays.copyOfRange(payload, 14, (int) (14 + messageLength)))).trim();
System.out.println(xmlMessage);
//Create XML document Object
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
DocumentBuilder db;
@@ -179,4 +179,8 @@ public class XMLGenerator {
public RegattaXMLTemplate getRegatta() {
return regatta;
}
public RaceXMLTemplate getRace() {
return race;
}
}
+233 -27
View File
@@ -1,29 +1,43 @@
package seng302.utilities;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;
import javafx.scene.paint.Color;
import javafx.util.Pair;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;
import seng302.model.ClientYacht;
import seng302.model.Colors;
import seng302.model.Limit;
import seng302.model.mark.CompoundMark;
import seng302.model.mark.Corner;
import seng302.model.mark.Mark;
import seng302.model.stream.xml.generator.RaceXMLTemplate;
import seng302.model.stream.xml.generator.RegattaXMLTemplate;
import seng302.model.stream.xml.parser.RaceXMLData;
import seng302.model.stream.xml.parser.RegattaXMLData;
import seng302.model.token.Token;
import seng302.model.token.TokenType;
import seng302.visualiser.fxObjects.assets_3D.BoatMeshType;
/**
* Utilities for parsing XML documents
*/
public class XMLParser {
private static final int MAX_PLAYERS = 8;
/**
* Returns the text content of a given child element tag, assuming it exists, as an Integer.
*
@@ -34,7 +48,7 @@ public class XMLParser {
private static Integer getElementInt(Element ele, String tag) {
NodeList tagList = ele.getElementsByTagName(tag);
if (tagList.getLength() > 0) {
return Integer.parseInt(tagList.item(0).getTextContent());
return Integer.parseInt(tagList.item(0).getTextContent().replaceAll("\\s+",""));
} else {
return null;
}
@@ -66,7 +80,7 @@ public class XMLParser {
private static Double getElementDouble(Element ele, String tag) {
NodeList tagList = ele.getElementsByTagName(tag);
if (tagList.getLength() > 0) {
return Double.parseDouble(tagList.item(0).getTextContent());
return Double.parseDouble(tagList.item(0).getTextContent().replaceAll("\\s+",""));
} else {
return null;
}
@@ -138,17 +152,27 @@ public class XMLParser {
Node currentBoat = boatsList.item(i);
if (currentBoat.getNodeName().equals("Boat")) {
// Boat boat = new Boat(currentBoat);
BoatMeshType boatMeshType;
try {
boatMeshType = BoatMeshType.valueOf(XMLParser.getNodeAttributeString(currentBoat, "Type"));
} catch (IllegalArgumentException e){
boatMeshType = BoatMeshType.DINGHY;
}
Color color;
try {
color = Color.web(getNodeAttributeString(currentBoat, "Color"));
} catch (NullPointerException npe) {
color = Colors.getColor(new Random().nextInt(8));
}
ClientYacht yacht = new ClientYacht(
XMLParser.getNodeAttributeString(currentBoat, "Type"),
boatMeshType,
XMLParser.getNodeAttributeInt(currentBoat, "SourceID"),
XMLParser.getNodeAttributeString(currentBoat, "HullNum"),
XMLParser.getNodeAttributeString(currentBoat, "ShortName"),
XMLParser.getNodeAttributeString(currentBoat, "BoatName"),
XMLParser.getNodeAttributeString(currentBoat, "Country"));
yacht.setColour(Color.web(getNodeAttributeString(currentBoat, "Color")));
if (yacht.getBoatType().equals("Yacht")) {
competingBoats.put(yacht.getSourceId(), yacht);
}
yacht.setColour(color);
competingBoats.put(yacht.getSourceId(), yacht);
}
}
return competingBoats;
@@ -174,6 +198,31 @@ public class XMLParser {
);
}
public static Boolean tokensEnabled(Document doc) {
Element docEle = doc.getDocumentElement();
try {
Node tokenNode = docEle.getAttributeNode("Tokens");
return Boolean.valueOf(XMLParser.getNodeAttributeString(tokenNode, "Enabled"));
} catch (NullPointerException npe) {
return false;
}
}
public static Integer getMaxPlayers(Document doc) {
Element docEle = doc.getDocumentElement();
try {
NamedNodeMap namedNodeMap = docEle.getElementsByTagName("Participants").item(0).getAttributes();
Node node = namedNodeMap.getNamedItem("MaxPlayers");
if (node != null) {
return Integer.parseInt(node.getNodeValue());
}
} catch (NullPointerException npe) {
npe.printStackTrace();
return MAX_PLAYERS;
}
return MAX_PLAYERS;
}
/**
* Returns an object containing the data extracted from the given xml formatted document
*
@@ -183,7 +232,7 @@ public class XMLParser {
public static RaceXMLData parseRace(Document doc) {
Element docEle = doc.getDocumentElement();
return new RaceXMLData(
extractParticpantIDs(docEle),
extractParticipantIDs(docEle),
extractTokens(docEle),
extractCompoundMarks(docEle),
extractMarkOrder(docEle),
@@ -196,17 +245,20 @@ public class XMLParser {
*/
private static List<Token> extractTokens(Element docEle) {
List<Token> tokens = new ArrayList<>();
NodeList tokenList = docEle.getElementsByTagName("Tokens").item(0).getChildNodes();
for (int i = 0; i < tokenList.getLength(); i++) {
Node tokenNode = tokenList.item(i);
if (tokenNode.getNodeName().equals("Token")) {
String tokenType = getNodeAttributeString(tokenNode, "TokenType");
Double lat = getNodeAttributeDouble(tokenNode, "TargetLat");
Double lng = getNodeAttributeDouble(tokenNode, "TargetLng");
tokens.add(new Token(TokenType.valueOf(tokenType), lat, lng));
try {
NodeList tokenList = docEle.getElementsByTagName("Tokens").item(0).getChildNodes();
for (int i = 0; i < tokenList.getLength(); i++) {
Node tokenNode = tokenList.item(i);
if (tokenNode.getNodeName().equals("Token")) {
String tokenType = getNodeAttributeString(tokenNode, "TokenType");
Double lat = getNodeAttributeDouble(tokenNode, "TargetLat");
Double lng = getNodeAttributeDouble(tokenNode, "TargetLng");
tokens.add(new Token(TokenType.valueOf(tokenType), lat, lng));
}
}
} catch (NullPointerException npe) {
return new ArrayList<>();
}
return tokens;
}
@@ -219,13 +271,11 @@ public class XMLParser {
for (int i = 0; i < limitList.getLength(); i++) {
Node limitNode = limitList.item(i);
if (limitNode.getNodeName().equals("Limit")) {
courseLimit.add(
new Limit(
XMLParser.getNodeAttributeInt(limitNode, "SeqID"),
XMLParser.getNodeAttributeDouble(limitNode, "Lat"),
XMLParser.getNodeAttributeDouble(limitNode, "Lon")
)
);
courseLimit.add(new Limit(
XMLParser.getNodeAttributeInt(limitNode, "SeqID"),
XMLParser.getNodeAttributeDouble(limitNode, "Lat"),
XMLParser.getNodeAttributeDouble(limitNode, "Lon")
));
}
}
return courseLimit;
@@ -257,7 +307,7 @@ public class XMLParser {
/**
* Extracts course participants data
*/
private static List<Integer> extractParticpantIDs (Element docEle) {
private static List<Integer> extractParticipantIDs(Element docEle) {
List<Integer> boatIDs = new ArrayList<>();
NodeList pList = docEle.getElementsByTagName("Participants").item(0).getChildNodes();
for (int i = 0; i < pList.getLength(); i++) {
@@ -279,10 +329,11 @@ public class XMLParser {
for (int i = 0; i < cMarkList.getLength(); i++) {
Node cMarkNode = cMarkList.item(i);
if (cMarkNode.getNodeName().equals("CompoundMark")) {
String name = XMLParser.getNodeAttributeString(cMarkNode, "Name");
name = (name == null || name.equals("")) ? "Mark " + i+1: name;
cMark = new CompoundMark(
XMLParser.getNodeAttributeInt(cMarkNode, "CompoundMarkID"),
XMLParser.getNodeAttributeString(cMarkNode, "Name"),
createMarks(cMarkNode)
name, createMarks(cMarkNode)
);
allMarks.add(cMark);
}
@@ -303,14 +354,169 @@ public class XMLParser {
Node markNode = childMarks.item(i);
if (markNode.getNodeName().equals("Mark")) {
Integer seqID = XMLParser.getNodeAttributeInt(markNode, "SeqID");
seqID = (seqID == null) ? i+1 : seqID;
Integer sourceID = XMLParser.getNodeAttributeInt(markNode, "SourceID");
sourceID = (sourceID == null) ? i+1 : sourceID;
String markName = XMLParser.getNodeAttributeString(markNode, "Name");
markName = (markName == null || markName.equals("")) ? cMarkName + " " + i+1: markName;
Double targetLat = XMLParser.getNodeAttributeDouble(markNode, "TargetLat");
Double targetLng = XMLParser.getNodeAttributeDouble(markNode, "TargetLng");
Mark mark = new Mark(markName, seqID, targetLat, targetLng, sourceID);
subMarks.add(mark);
}
}
return subMarks;
}
/**
* This ungodly combination of existing methods and code blocks parses a race definition file.
* Look upon it and despair.
* @param url the location of the race def file
* @param serverName the name of the server
* @param repetitions the repetitions of a segment of the race def file.
* @param maxPlayers max number of players. uses the default race max if null or greater than the actual max.
* @param tokensEnabled if tokens are enabled
* @return a pair which contains regatta string, race string as key, value pair.
*/
public static Pair<RegattaXMLTemplate, RaceXMLTemplate> parseRaceDef(
String url, String serverName, Integer repetitions, Integer maxPlayers, Boolean tokensEnabled
) {
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
DocumentBuilder db;
Document doc = null;
try {
db = dbf.newDocumentBuilder();
doc = db.parse(url);
} catch (ParserConfigurationException | IOException | SAXException e) {
e.printStackTrace();
}
Element docEle = doc.getDocumentElement();
RegattaXMLTemplate regattaXMLTemplate = new RegattaXMLTemplate(
serverName, XMLParser.getElementString(docEle, "CourseName"),
XMLParser.getElementDouble(docEle, "CentralLat"),
XMLParser.getElementDouble(docEle, "CentralLng")
);
XMLGenerator xmlGenerator = new XMLGenerator();
xmlGenerator.setRegattaTemplate(regattaXMLTemplate);
if (maxPlayers == null) {
maxPlayers = XMLParser.getElementInt(docEle, "MaxPlayers");
} else if (maxPlayers > XMLParser.getElementInt(docEle, "MaxPlayers")) {
maxPlayers = XMLParser.getElementInt(docEle, "MaxPlayers");
}
RaceXMLTemplate raceXMLTemplate = new RaceXMLTemplate(
new ArrayList<>(), new ArrayList<>(),
XMLParser.extractMarkOrderRaceDef(docEle, repetitions),
XMLParser.extractCourseLimitRaceDef(docEle),
XMLParser.extractCompoundMarksRaceDef(docEle),
maxPlayers, tokensEnabled
);
xmlGenerator.setRaceTemplate(raceXMLTemplate);
return new Pair<>(regattaXMLTemplate, raceXMLTemplate);
}
private static List<Corner> extractMarkOrderRaceDef(Element docEle, int repitions){
List<Corner> compoundMarkSequence = new ArrayList<>();
NodeList cornerList = docEle.getElementsByTagName("Course").item(0).getChildNodes();
int seqId = 1;
final int zoneSize = 3;
for (int i=0; i<cornerList.getLength(); i++) {
Node segment = cornerList.item(i);
if (segment.getNodeName().equals("OpeningSegment") ||
segment.getNodeName().equals("ClosingSegment")) {
seqId = parseCourseSegment(segment, seqId, compoundMarkSequence);
} else if (segment.getNodeName().equals("RepeatingSegment")) {
for (int k = 0; k < repitions; k++) {
seqId = parseCourseSegment(segment, seqId, compoundMarkSequence);
}
}
}
return compoundMarkSequence;
}
/**
* Parses a segment of the course adding new Corners to the given list.
* @param segment Segment to parse
* @param seqID initial sequence ID
* @param course course to add corners to
* @return the last sequence id.
*/
private static int parseCourseSegment(Node segment, int seqID, List<Corner> course) {
NodeList segmentList = segment.getChildNodes();
for (int j = 0; j < segmentList.getLength(); j++) {
Node corner = segmentList.item(j);
if (corner.getNodeName().equals("Corner")) {
String rounding = XMLParser.getNodeAttributeString(corner, "Rounding");
rounding = //Converting "P" to "Port" and "S" to "Stbd"
rounding.equals("P") ? "Port" :
rounding.equals("S") ? "Stbd" : rounding;
course.add(new Corner(
seqID++, XMLParser.getNodeAttributeInt(corner, "CompoundMarkID"),
rounding, 3
));
}
}
return seqID;
}
private static List<Limit> extractCourseLimitRaceDef(Element docEle) {
List<Limit> courseLimit = new ArrayList<>();
NodeList limitList = docEle.getElementsByTagName("CourseLimit").item(0).getChildNodes();
int seqId = 1;
for (int i = 0; i < limitList.getLength(); i++) {
Node limitNode = limitList.item(i);
if (limitNode.getNodeName().equals("Limit")) {
courseLimit.add(new Limit(
seqId++, XMLParser.getNodeAttributeDouble(limitNode, "Lat"),
XMLParser.getNodeAttributeDouble(limitNode, "Lng")
));
}
}
return courseLimit;
}
private static List<CompoundMark> extractCompoundMarksRaceDef(Element docEle){
List<CompoundMark> allMarks = new ArrayList<>();
NodeList cMarkList = docEle.getElementsByTagName("Marks").item(0).getChildNodes();
CompoundMark cMark;
int markCount = 200;
for (int i = 0; i < cMarkList.getLength(); i++) {
Node cMarkNode = cMarkList.item(i);
if (cMarkNode.getNodeName().equals("CompoundMark")) {
Integer id = XMLParser.getNodeAttributeInt(cMarkNode, "CompoundMarkID");
List<Mark> subMarks = createMarksRaceDef(cMarkNode, markCount,"Mark " + id);
markCount += subMarks.size();
allMarks.add(new CompoundMark(id, "Mark " + id, subMarks));
}
}
return allMarks;
}
private static List<Mark> createMarksRaceDef(Node compoundMark, int markCount, String markName) {
List<Mark> subMarks = new ArrayList<>();
NodeList childMarks = compoundMark.getChildNodes();
int seqID = 1;
for (int i = 0; i < childMarks.getLength(); i++) {
Node markNode = childMarks.item(i);
if (markNode.getNodeName().equals("Mark")) {
Double targetLat = XMLParser.getNodeAttributeDouble(markNode, "Lat");
Double targetLng = XMLParser.getNodeAttributeDouble(markNode, "Lng");
Mark mark = new Mark(markName + " subMark " + seqID, seqID, targetLat, targetLng, markCount++);
subMarks.add(mark);
seqID += 1;
}
}
return subMarks;
}
}
@@ -14,6 +14,7 @@ import java.util.TimerTask;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.zip.CRC32;
import java.util.zip.Checksum;
import javafx.util.Pair;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import seng302.gameServer.messages.BoatAction;
@@ -25,8 +26,14 @@ import seng302.gameServer.messages.CustomizeRequestType;
import seng302.gameServer.messages.Message;
import seng302.gameServer.messages.RegistrationRequestMessage;
import seng302.gameServer.messages.RegistrationResponseStatus;
import seng302.gameServer.messages.XMLMessage;
import seng302.gameServer.messages.XMLMessageSubType;
import seng302.model.stream.packets.PacketType;
import seng302.model.stream.packets.StreamPacket;
import seng302.model.stream.xml.generator.RaceXMLTemplate;
import seng302.model.stream.xml.generator.RegattaXMLTemplate;
import seng302.utilities.XMLGenerator;
import seng302.utilities.XMLParser;
/**
* A class describing a single connection to a Server for the purposes of sending and receiving on
@@ -44,7 +51,7 @@ public class ClientToServerThread implements Runnable {
@FunctionalInterface
public interface DisconnectedFromHostListener {
void notifYDisconnection (String message);
void notifyDisconnection(String message);
}
private class ByteReadException extends Exception {
@@ -68,7 +75,7 @@ public class ClientToServerThread implements Runnable {
private Timer upWindPacketTimer = new Timer();
private Timer downWindPacketTimer = new Timer();
private boolean upwindTimerFlag = false, downwindTimerFlag = false;
static public final int PACKET_SENDING_INTERVAL_MS = 100;
public static final int PACKET_SENDING_INTERVAL_MS = 100;
private int clientId = -1;
@@ -166,7 +173,7 @@ public class ClientToServerThread implements Runnable {
private void notifyDisconnectListeners (String message) {
if (socketOpen) {
for (DisconnectedFromHostListener listener : disconnectionListeners) {
listener.notifYDisconnection(message);
listener.notifyDisconnection(message);
}
}
}
@@ -175,7 +182,7 @@ public class ClientToServerThread implements Runnable {
* Sends a request to the server asking for a source ID
*/
private void sendRegistrationRequest() {
RegistrationRequestMessage requestMessage = new RegistrationRequestMessage(ClientType.PLAYER);
RegistrationRequestMessage requestMessage = new RegistrationRequestMessage(ClientType.PLAYER, clientId);
try {
os.write(requestMessage.getBuffer());
@@ -191,9 +198,8 @@ public class ClientToServerThread implements Runnable {
* @param packet The registration requests packet
*/
private void processRegistrationResponse(StreamPacket packet){
int sourceId = (int) Message.bytesToLong(Arrays.copyOfRange(packet.getPayload(), 0, 3));
int sourceId = (int) Message.bytesToLong(Arrays.copyOfRange(packet.getPayload(), 0, 4));
int statusCode = (int) Message.bytesToLong(Arrays.copyOfRange(packet.getPayload(), 4,5));
RegistrationResponseStatus status = RegistrationResponseStatus.getResponseStatus(statusCode);
if (status.equals(RegistrationResponseStatus.SUCCESS_PLAYING)){
@@ -243,7 +249,7 @@ public class ClientToServerThread implements Runnable {
new TimerTask() {
@Override
public void run() {
sendBoatActionMessage(new BoatActionMessage(BoatAction.DOWNWIND));
sendBoatActionMessage(new BoatActionMessage(BoatAction.DOWNWIND, clientId));
}
}, 0, PACKET_SENDING_INTERVAL_MS
);
@@ -256,14 +262,14 @@ public class ClientToServerThread implements Runnable {
new TimerTask() {
@Override
public void run() {
sendBoatActionMessage(new BoatActionMessage(BoatAction.UPWIND));
sendBoatActionMessage(new BoatActionMessage(BoatAction.UPWIND, clientId));
}
}, 0, PACKET_SENDING_INTERVAL_MS
);
}
break;
default:
sendBoatActionMessage(new BoatActionMessage(actionType));
sendBoatActionMessage(new BoatActionMessage(actionType, clientId));
break;
}
}
@@ -369,4 +375,25 @@ public class ClientToServerThread implements Runnable {
public int getClientId () {
return clientId;
}
public void sendXML(String path, String serverName, Integer legRepeats, Integer maxPlayers, Boolean tokensEnabled) {
Pair<RegattaXMLTemplate, RaceXMLTemplate> regattaRace = XMLParser.parseRaceDef(
path, serverName, legRepeats, maxPlayers, tokensEnabled
);
XMLGenerator xmlGenerator = new XMLGenerator();
xmlGenerator.setRegattaTemplate(regattaRace.getKey());
xmlGenerator.setRaceTemplate(regattaRace.getValue());
String regatta = xmlGenerator.getRegattaAsXml();
String race = xmlGenerator.getRaceAsXml();
sendByteBuffer(
new XMLMessage(
regatta, XMLMessageSubType.REGATTA, regatta.length()
).getBuffer()
);
sendByteBuffer(
new XMLMessage(
race, XMLMessageSubType.RACE, race.length()
).getBuffer()
);
}
}
@@ -14,9 +14,6 @@ import javafx.application.Platform;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.fxml.FXMLLoader;
import javafx.scene.Node;
import javafx.scene.control.Alert;
import javafx.scene.control.Alert.AlertType;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.scene.layout.Pane;
@@ -29,6 +26,8 @@ import seng302.gameServer.messages.BoatAction;
import seng302.gameServer.messages.BoatStatus;
import seng302.gameServer.messages.YachtEventType;
import seng302.model.ClientYacht;
import seng302.model.GameKeyBind;
import seng302.model.KeyAction;
import seng302.model.RaceState;
import seng302.model.stream.packets.StreamPacket;
import seng302.model.stream.parser.MarkRoundingData;
@@ -42,10 +41,10 @@ import seng302.utilities.Sounds;
import seng302.utilities.StreamParser;
import seng302.utilities.XMLGenerator;
import seng302.utilities.XMLParser;
import seng302.visualiser.controllers.FinishScreenViewController;
import seng302.visualiser.controllers.LobbyController;
import seng302.visualiser.controllers.RaceViewController;
import seng302.visualiser.controllers.ViewManager;
import seng302.visualiser.controllers.dialogs.PopupDialogController;
/**
* This class is a client side instance of a yacht racing game in JavaFX. The game is instantiated
@@ -64,9 +63,12 @@ public class GameClient {
private RaceXMLData courseData;
private RaceState raceState = new RaceState();
private LobbyController lobbyController;
private RaceViewController raceViewController;
private ArrayList<ClientYacht> finishedBoats = new ArrayList<>();
private GameKeyBind gameKeyBind; // all the key binding setting.
private ObservableList<String> clientLobbyList = FXCollections.observableArrayList();
/**
@@ -76,17 +78,7 @@ public class GameClient {
*/
public GameClient(Pane holder) {
this.holderPane = holder;
// if (holderPane.getParent() == null) {
// this.holderPane.parentProperty().addListener(((observable, oldValue, newValue) -> {
// if (newValue != null) {
// newValue.getScene().setOnKeyPressed(this::keyPressed);
// newValue.getScene().setOnKeyReleased(this::keyReleased);
// }
// }));
// } else {
// this.holderPane.getParent().getScene().setOnKeyPressed(this::keyPressed);
// this.holderPane.getParent().getScene().setOnKeyReleased(this::keyReleased);
// }
this.gameKeyBind = GameKeyBind.getInstance();
}
/**
@@ -117,22 +109,10 @@ public class GameClient {
ViewManager.getInstance().setProperty("serverName", regattaData.getRegattaName());
ViewManager.getInstance().setProperty("mapName", regattaData.getCourseName());
// TODO disable ready button;
//LobbyController_old lobbyController = loadLobby();
//lobbyController.setSocketThread(socketThread);
//lobbyController.setPlayerID(socketThread.getClientId());
//lobbyController.setPlayerListSource(clientLobbyList);
//lobbyController.disableReadyButton();
// lobbyController.addCloseListener((exitCause) -> this.loadStartScreen());
this.lobbyController = ViewManager.getInstance().goToLobby(true);
} catch (IOException ioe) {
showConnectionError("Unable to find server");
//Platform.runLater(this::loadStartScreen);
}
}
@@ -141,9 +121,11 @@ public class GameClient {
* @param ipAddress IP to connect to.
* @param portNumber Port to connect to.
*/
public ServerDescription runAsHost(String ipAddress, Integer portNumber, String serverName, Integer maxPlayers) {
public ServerDescription runAsHost(
String ipAddress, Integer portNumber, String serverName, Integer maxPlayers, String race,
Integer numLegs, Boolean tokensEnabled
) {
XMLGenerator.setDefaultRaceName(serverName);
GameState.setMaxPlayers(maxPlayers);
server = new MainServerThread();
@@ -152,7 +134,7 @@ public class GameClient {
} catch (IOException e) {
showConnectionError("Cannot connect to server as host");
}
socketThread.sendXML(race, serverName, numLegs, maxPlayers, tokensEnabled);
while (regattaData == null){
try {
Thread.sleep(100);
@@ -188,10 +170,12 @@ public class GameClient {
private void showConnectionError (String message) {
Platform.runLater(() -> {
Alert alert = new Alert(AlertType.ERROR);
alert.setHeaderText("Connection Error");
alert.setContentText(message);
alert.showAndWait();
PopupDialogController controller = ViewManager.getInstance().showPopupDialog();
controller.setHeader("Oops");
controller.setContent(message);
controller.setOptionButtonText("GO HOME");
controller
.setOptionButtonEventHandler(event -> ViewManager.getInstance().goToStartView());
});
}
@@ -200,41 +184,8 @@ public class GameClient {
socketThread.addStreamObserver(this::parsePackets);
}
private void loadRaceView() {
FXMLLoader fxmlLoader = loadFXMLToHolder("/views/RaceView.fxml");
holderPane.getScene().setOnKeyPressed(this::keyPressed);
holderPane.getScene().setOnKeyReleased(this::keyReleased);
raceView = fxmlLoader.getController();
ClientYacht player = allBoatsMap.get(socketThread.getClientId());
raceView.loadRace(allBoatsMap, courseData, raceState, player);
}
private void loadFinishScreenView() {
Sounds.stopMusic();
Sounds.stopSoundEffects();
Sounds.playFinishMusic();
FXMLLoader fxmlLoader = loadFXMLToHolder("/views/FinishScreenView.fxml");
FinishScreenViewController controller = fxmlLoader.getController();
controller.setFinishers(raceState.getPlayerPositions());
}
private FXMLLoader loadFXMLToHolder(String fxmlLocation) {
FXMLLoader fxmlLoader = new FXMLLoader(
getClass().getResource(fxmlLocation)
);
try {
final Node fxmlLoaderFX = fxmlLoader.load();
Platform.runLater(() -> {
holderPane.getChildren().clear();
holderPane.getChildren().add(fxmlLoaderFX);
});
} catch (IOException e) {
e.printStackTrace();
}
return fxmlLoader;
public void setRaceViewController(RaceViewController controller) {
this.raceViewController = controller;
}
private void parsePackets() {
@@ -263,6 +214,7 @@ public class GameClient {
break;
case RACE_XML:
System.out.println("HEY I GOT A RACE MANG AND I AM CLIENT " + ((Boolean) (server==null)).toString());
RaceXMLData raceXMLData = XMLParser.parseRace(
StreamParser.extractXmlMessage(packet)
);
@@ -398,10 +350,13 @@ public class GameClient {
}
if (raceFinished) {
raceViewController.showFinishDialog(finishedBoats);
Sounds.playFinishSound();
close();
loadFinishScreenView();
ViewManager.getInstance().getGameClient().stopGame();
//loadFinishScreenView();
}
raceState.setRaceFinished();
}
}
@@ -416,7 +371,6 @@ public class GameClient {
socketThread.setSocketToClose();
}
/**
* Handle the key-pressed event from the text field.
* @param e The key event triggering this call
@@ -428,16 +382,16 @@ public class GameClient {
}
return;
}
switch (e.getCode()) {
case SPACE: // align with vmg
socketThread.sendBoatAction(BoatAction.VMG); break;
case PAGE_UP: // upwind
socketThread.sendBoatAction(BoatAction.UPWIND); break;
case PAGE_DOWN: // downwind
socketThread.sendBoatAction(BoatAction.DOWNWIND); break;
case ENTER: // tack/gybe
// if chat box is active take whatever is in there and send it to server
socketThread.sendBoatAction(BoatAction.TACK_GYBE); break;
if (gameKeyBind.getKeyCode(KeyAction.VMG) == e.getCode()) { // align with vmg
socketThread.sendBoatAction(BoatAction.VMG);
} else if (gameKeyBind.getKeyCode(KeyAction.UPWIND) == e.getCode()) { // upwind
socketThread.sendBoatAction(BoatAction.UPWIND);
} else if (gameKeyBind.getKeyCode(KeyAction.DOWNWIND) == e.getCode()) { // downwind
socketThread.sendBoatAction(BoatAction.DOWNWIND);
} else if (gameKeyBind.getKeyCode(KeyAction.TACK_GYBE) == e.getCode()) { // tack/gybe
// if chat box is active take whatever is in there and send it to server
socketThread.sendBoatAction(BoatAction.TACK_GYBE);
}
}
@@ -446,15 +400,17 @@ public class GameClient {
if (raceView.isChatInputFocused()) {
return;
}
switch (e.getCode()) {
//TODO 12/07/17 Determine the sail state and send the appropriate packet (eg. if sails are in, send a sail out packet)
case SHIFT: // sails in/sails out
if (gameKeyBind.getKeyCode(KeyAction.SAILS_STATE) == e.getCode()) { // sails in/sails out
if (allBoatsMap.get(socketThread.getClientId()).getSailIn()) {
socketThread.sendBoatAction(BoatAction.SAILS_OUT);
} else {
socketThread.sendBoatAction(BoatAction.SAILS_IN);
allBoatsMap.get(socketThread.getClientId()).toggleSail();
break;
case PAGE_UP:
case PAGE_DOWN:
socketThread.sendBoatAction(BoatAction.MAINTAIN_HEADING); break;
}
allBoatsMap.get(socketThread.getClientId()).toggleSail();
} else if (gameKeyBind.getKeyCode(KeyAction.UPWIND) == e.getCode()
|| gameKeyBind.getKeyCode(KeyAction.DOWNWIND) == e.getCode()) {
socketThread.sendBoatAction(BoatAction.MAINTAIN_HEADING);
}
}
@@ -504,9 +460,21 @@ public class GameClient {
GameState.setCurrentStage(GameStages.CANCELLED);
if (server != null) server.terminate();
if (socketThread != null) socketThread.setSocketToClose();
server = null;
// socketThread = null;
}
public Map<Integer, ClientYacht> getAllBoatsMap() {
return allBoatsMap;
}
public void sendToggleTurningModePacket() {
if (socketThread != null) {
if (gameKeyBind.isContinuouslyTurning()) {
socketThread.sendBoatAction(BoatAction.CONTINUOUSLY_TURNING);
} else {
socketThread.sendBoatAction(BoatAction.DEFAULT_TURNING);
}
}
}
}
+19 -430
View File
@@ -1,442 +1,31 @@
package seng302.visualiser;
import javafx.application.Platform;
import javafx.collections.ObservableList;
import javafx.geometry.Point2D;
import javafx.scene.*;
import javafx.scene.image.ImageView;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.paint.Paint;
import javafx.scene.shape.Polygon;
import seng302.gameServer.messages.RoundingSide;
import seng302.model.GeoPoint;
import java.util.ArrayList;
import java.util.List;
import javafx.scene.Group;
import javafx.scene.Node;
import seng302.model.Limit;
import seng302.model.ScaledPoint;
import seng302.model.mark.CompoundMark;
import seng302.model.mark.Corner;
import seng302.model.mark.Mark;
import seng302.utilities.GeoUtility;
import seng302.visualiser.fxObjects.assets_2D.*;
import java.util.*;
/**
* Created by cir27 on 20/07/17.
* Common elements of game visualizing classes.
*/
public class GameView extends Pane {
public abstract class GameView {
private double bufferSize = 50;
private double horizontalBuffer = 0;
double canvasWidth, canvasHeight;
ScaledPoint scaledPoint;
private double canvasWidth = 1100;
private double canvasHeight = 920;
private boolean horizontalInversion = false;
List<Limit> borderPoints;
Group gameObjects = new Group();
Group markers = new Group();
Group tokens = new Group();
List<CompoundMark> course = new ArrayList<>();
List<CompoundMark> compoundMarks = new ArrayList<>();
List<Corner> courseOrder = new ArrayList<>();
private double distanceScaleFactor;
private ScaleDirection scaleDirection;
private GeoPoint minLatPoint, minLonPoint, maxLatPoint, maxLonPoint;
private double referencePointX, referencePointY;
private Polygon raceBorder = new CourseBoundary();
/* Note that if either of these is null then values for it have not been added and the other
should be used as the limits of the map. */
private List<Limit> borderPoints;
private Map<Mark, Marker2D> markerObjects;
private ObservableList<Node> gameObjects;
private Group markers = new Group();
private Group tokens = new Group();
private List<CompoundMark> course = new ArrayList<>();
private ImageView mapImage = new ImageView();
private enum ScaleDirection {
HORIZONTAL,
VERTICAL
}
public GameView () {
gameObjects = this.getChildren();
gameObjects.addAll(mapImage, raceBorder, markers, tokens);
}
/**
* Adds a course to the GameView. The view is scaled accordingly unless a border is set in which
* case the course is added relative ot the border.
*
* @param newCourse the mark objects that make up the course.
* @param sequence The sequence the marks travel through
*/
public void updateCourse(List<CompoundMark> newCourse, List<Corner> sequence) {
markerObjects = new HashMap<>();
for (Corner corner : sequence) { //Makes course out of all compound marks.
for (CompoundMark compoundMark : newCourse) {
if (corner.getCompoundMarkID() == compoundMark.getId()) {
course.add(compoundMark);
}
}
}
// TODO: 16/08/17 Updating mark roundings here. It should not happen here. Nor should it be done this way.
for (Corner corner : sequence){
CompoundMark compoundMark = course.get(corner.getSeqID() - 1);
compoundMark.setRoundingSide(
RoundingSide.getRoundingSide(corner.getRounding())
);
}
final List<Gate> gates = new ArrayList<>();
Paint colour = Color.BLACK;
//Creates new markers
for (CompoundMark cMark : newCourse) {
//Set start and end colour
if (cMark.getId() == sequence.get(0).getCompoundMarkID()) {
colour = Color.GREEN;
} else if (cMark.getId() == sequence.get(sequence.size() - 1).getCompoundMarkID()) {
colour = Color.RED;
}
//Create mark dots
for (Mark mark : cMark.getMarks()) {
makeAndBindMarker(mark, colour);
}
//Create gate line
if (cMark.isGate()) {
for (int i = 1; i < cMark.getMarks().size(); i++) {
gates.add(
makeAndBindGate(
markerObjects.get(cMark.getSubMark(i)),
markerObjects.get(cMark.getSubMark(i + 1)),
colour
)
);
}
}
colour = Color.BLACK;
}
createMarkArrows(sequence);
//Scale race to markers if there is no border.
if (borderPoints == null) {
rescaleRace(new ArrayList<>(markerObjects.keySet()));
}
//Move the Markers to initial position.
markerObjects.forEach(((mark, marker2D) -> {
Point2D p2d = findScaledXY(mark.getLat(), mark.getLng());
marker2D.setLayoutX(p2d.getX());
marker2D.setLayoutY(p2d.getY());
}));
Platform.runLater(() -> {
markers.getChildren().clear();
markers.getChildren().addAll(gates);
markers.getChildren().addAll(markerObjects.values());
});
}
/**
* Calculates all the data needed for to create mark arrows. Requires that a course has been
* added to the gameview.
* @param sequence The order in which marks are traversed.
*/
private void createMarkArrows (List<Corner> sequence) {
for (int i=1; i < sequence.size()-1; i++) { //General case.
double averageLat = 0;
double averageLng = 0;
int numMarks = course.get(i-1).getMarks().size();
for (Mark mark : course.get(i-1).getMarks()) {
averageLat += mark.getLat();
averageLng += mark.getLng();
}
GeoPoint lastMarkAv = new GeoPoint(averageLat / numMarks, averageLng / numMarks);
numMarks = course.get(i+1).getMarks().size();
averageLat = 0;
averageLng = 0;
for (Mark mark : course.get(i+1).getMarks()) {
averageLat += mark.getLat();
averageLng += mark.getLng();
}
GeoPoint nextMarkAv = new GeoPoint(averageLat / numMarks, averageLng / numMarks);
// TODO: 16/08/17 This comparison doesn't need to exist but the alternative is to user server enum client side.
for (Mark mark : course.get(i).getMarks()) {
markerObjects.get(mark).addArrows(
mark.getRoundingSide() == RoundingSide.STARBOARD ? MarkArrowFactory.RoundingSide.STARBOARD : MarkArrowFactory.RoundingSide.PORT,
GeoUtility.getBearing(lastMarkAv, mark),
GeoUtility.getBearing(mark, nextMarkAv)
);
}
}
createStartLineArrows();
createFinishLineArrows();
}
private void createStartLineArrows () {
double averageLat = 0;
double averageLng = 0;
int numMarks = 0;
for (Mark mark : course.get(1).getMarks()) {
numMarks += 1;
averageLat += mark.getLat();
averageLng += mark.getLng();
}
GeoPoint firstMarkAv = new GeoPoint(averageLat / numMarks, averageLng / numMarks);
for (Mark mark : course.get(0).getMarks()) {
markerObjects.get(mark).addArrows(
mark.getRoundingSide() == RoundingSide.STARBOARD ? MarkArrowFactory.RoundingSide.STARBOARD : MarkArrowFactory.RoundingSide.PORT,
0d, //90
GeoUtility.getBearing(mark, firstMarkAv)
);
}
}
private void createFinishLineArrows () {
double numMarks = 0;
double averageLat = 0;
double averageLng = 0;
for (Mark mark : course.get(course.size()-2).getMarks()) {
numMarks += 1;
averageLat += mark.getLat();
averageLng += mark.getLng();
}
GeoPoint secondToLastMarkAv = new GeoPoint(averageLat / numMarks, averageLng / numMarks);
for (Mark mark : course.get(course.size()-1).getMarks()) {
markerObjects.get(mark).addArrows(
mark.getRoundingSide() == RoundingSide.STARBOARD ? MarkArrowFactory.RoundingSide.STARBOARD : MarkArrowFactory.RoundingSide.PORT,
GeoUtility.getBearing(secondToLastMarkAv, mark),
GeoUtility.getBearing(mark, mark)
);
}
}
/**
* Creates a new Marker and binds it's position to the given Mark.
*
* @param observableMark The mark to bind the marker to.
* @param colour The desired colour of the mark
*/
private void makeAndBindMarker(Mark observableMark, Paint colour) {
Marker2D marker2D = new Marker2D(colour);
// marker.addArrows(MarkArrowFactory.RoundingSide.PORT, ThreadLocalRandom.current().nextDouble(91, 180), ThreadLocalRandom.current().nextDouble(1, 90));
markerObjects.put(observableMark, marker2D);
observableMark.addPositionListener((mark, lat, lon) -> {
Point2D p2d = findScaledXY(lat, lon);
markerObjects.get(mark).setLayoutX(p2d.getX());
markerObjects.get(mark).setLayoutY(p2d.getY());
});
}
/**
* Creates a new gate connecting the given marks.
*
* @param m1 The first Mark of the gate.
* @param m2 The second Mark of the gate.
* @param colour The desired colour of the gate.
* @return the new gate.
*/
private Gate makeAndBindGate(Marker2D m1, Marker2D m2, Paint colour) {
Gate gate = new Gate(colour);
gate.startXProperty().bind(
m1.layoutXProperty()
);
gate.startYProperty().bind(
m1.layoutYProperty()
);
gate.endXProperty().bind(
m2.layoutXProperty()
);
gate.endYProperty().bind(
m2.layoutYProperty()
);
return gate;
}
/**
* Adds a border to the GameView and rescales to the size of the border, does not rescale if a
* border already exists. Assumes the border is larger than the course.
*
* @param border the race border to be drawn.
*/
public void updateBorder(List<Limit> border) {
if (borderPoints == null) {
borderPoints = border;
rescaleRace(new ArrayList<>(borderPoints));
}
rescaleRace(new ArrayList<>(border));
List<Double> boundaryPoints = new ArrayList<>();
for (Limit limit : border) {
Point2D location = findScaledXY(limit.getLat(), limit.getLng());
boundaryPoints.add(location.getX());
boundaryPoints.add(location.getY());
}
raceBorder.getPoints().setAll(boundaryPoints);
}
/**
* Rescales the race to the size of the window.
*
* @param limitingCoordinates the set of geo points that contains the extremities of the race.
*/
public void rescaleRace(List<GeoPoint> limitingCoordinates) {
//Check is called once to avoid unnecessarily change the course limits once the race is running
findMinMaxPoint(limitingCoordinates);
double minLonToMaxLon = scaleRaceExtremities();
calculateReferencePointLocation(minLonToMaxLon);
}
/**
* Sets the class variables minLatPoint, maxLatPoint, minLonPoint, maxLonPoint to the point with
* the leftmost point, rightmost point, southern most point and northern most point
* respectively.
*/
private void findMinMaxPoint(List<GeoPoint> points) {
List<GeoPoint> sortedPoints = new ArrayList<>(points);
sortedPoints.sort(Comparator.comparingDouble(GeoPoint::getLat));
minLatPoint = new GeoPoint(sortedPoints.get(0).getLat(), sortedPoints.get(0).getLng());
GeoPoint maxLat = sortedPoints.get(sortedPoints.size() - 1);
maxLatPoint = new GeoPoint(maxLat.getLat(), maxLat.getLng());
sortedPoints.sort(Comparator.comparingDouble(GeoPoint::getLng));
minLonPoint = new GeoPoint(sortedPoints.get(0).getLat(), sortedPoints.get(0).getLng());
GeoPoint maxLon = sortedPoints.get(sortedPoints.size() - 1);
maxLonPoint = new GeoPoint(maxLon.getLat(), maxLon.getLng());
if (maxLonPoint.getLng() - minLonPoint.getLng() > 180) {
horizontalInversion = true;
}
}
/**
* Calculates the location of a reference point, this is always the point with minimum latitude,
* in relation to the canvas.
*
* @param minLonToMaxLon The horizontal distance between the point of minimum longitude to
* maximum longitude.
*/
private void calculateReferencePointLocation(double minLonToMaxLon) {
GeoPoint referencePoint = minLatPoint;
double referenceAngle;
if (scaleDirection == ScaleDirection.HORIZONTAL) {
referenceAngle = Math.abs(
GeoUtility.getBearingRad(referencePoint, minLonPoint)
);
referencePointX =
bufferSize + distanceScaleFactor * Math.sin(referenceAngle) * GeoUtility
.getDistance(referencePoint, minLonPoint);
referenceAngle = Math.abs(GeoUtility.getDistance(referencePoint, maxLatPoint));
referencePointY = canvasHeight - (bufferSize + bufferSize);
referencePointY -= distanceScaleFactor * Math.cos(referenceAngle) * GeoUtility
.getDistance(referencePoint, maxLatPoint);
referencePointY = referencePointY / 2;
referencePointY += bufferSize;
referencePointY += distanceScaleFactor * Math.cos(referenceAngle) * GeoUtility
.getDistance(referencePoint, maxLatPoint);
} else {
referencePointY = canvasHeight - bufferSize;
referenceAngle = Math.abs(
Math.toRadians(
GeoUtility.getDistance(referencePoint, minLonPoint)
)
);
referencePointX = bufferSize;
referencePointX += distanceScaleFactor * Math.sin(referenceAngle) * GeoUtility
.getDistance(referencePoint, minLonPoint);
referencePointX +=
((canvasWidth - (bufferSize + bufferSize)) - (minLonToMaxLon * distanceScaleFactor))
/ 2;
referencePointX += horizontalBuffer;
}
if (horizontalInversion) {
referencePointX = canvasWidth - bufferSize - (referencePointX - bufferSize);
}
}
/**
* Finds the scale factor necessary to fit all race markers within the onscreen map and assigns
* it to distanceScaleFactor Returns the max horizontal distance of the map.
*/
private double scaleRaceExtremities() {
double vertAngle = Math.abs(
GeoUtility.getBearingRad(minLatPoint, maxLatPoint)
);
double vertDistance =
Math.cos(vertAngle) * GeoUtility.getDistance(minLatPoint, maxLatPoint);
double horiAngle = Math.abs(
GeoUtility.getBearingRad(minLonPoint, maxLonPoint)
);
if (horiAngle <= (Math.PI / 2)) {
horiAngle = (Math.PI / 2) - horiAngle;
} else {
horiAngle = horiAngle - (Math.PI / 2);
}
double horiDistance =
Math.cos(horiAngle) * GeoUtility.getDistance(minLonPoint, maxLonPoint);
double vertScale = (canvasHeight - (bufferSize + bufferSize)) / vertDistance;
if ((horiDistance * vertScale) > (canvasWidth - (bufferSize + bufferSize))) {
distanceScaleFactor = (canvasWidth - (bufferSize + bufferSize)) / horiDistance;
scaleDirection = ScaleDirection.HORIZONTAL;
} else {
distanceScaleFactor = vertScale;
scaleDirection = ScaleDirection.VERTICAL;
}
return horiDistance;
}
private Point2D findScaledXY(double unscaledLat, double unscaledLon) {
double distanceFromReference;
double angleFromReference;
double xAxisLocation = referencePointX;
double yAxisLocation = referencePointY;
angleFromReference = GeoUtility.getBearingRad(
minLatPoint, new GeoPoint(unscaledLat, unscaledLon)
);
distanceFromReference = GeoUtility.getDistance(
minLatPoint, new GeoPoint(unscaledLat, unscaledLon)
);
if (angleFromReference >= 0 && angleFromReference <= Math.PI / 2) {
xAxisLocation += Math
.round(distanceScaleFactor * Math.sin(angleFromReference) * distanceFromReference);
yAxisLocation -= Math
.round(distanceScaleFactor * Math.cos(angleFromReference) * distanceFromReference);
} else if (angleFromReference >= 0) {
angleFromReference = angleFromReference - Math.PI / 2;
xAxisLocation += Math
.round(distanceScaleFactor * Math.cos(angleFromReference) * distanceFromReference);
yAxisLocation += Math
.round(distanceScaleFactor * Math.sin(angleFromReference) * distanceFromReference);
} else if (angleFromReference < 0 && angleFromReference >= -Math.PI / 2) {
angleFromReference = Math.abs(angleFromReference);
xAxisLocation -= Math
.round(distanceScaleFactor * Math.sin(angleFromReference) * distanceFromReference);
yAxisLocation -= Math
.round(distanceScaleFactor * Math.cos(angleFromReference) * distanceFromReference);
} else {
angleFromReference = Math.abs(angleFromReference) - Math.PI / 2;
xAxisLocation -= Math
.round(distanceScaleFactor * Math.cos(angleFromReference) * distanceFromReference);
yAxisLocation += Math
.round(distanceScaleFactor * Math.sin(angleFromReference) * distanceFromReference);
}
if (horizontalInversion) {
xAxisLocation = canvasWidth - bufferSize - (xAxisLocation - bufferSize);
}
return new Point2D(xAxisLocation, yAxisLocation);
}
public void setSize(Double width, Double height){
this.canvasWidth = width;
this.canvasHeight = height;
}
public void setHorizontalBuffer(Double buff){
this.horizontalBuffer = buff;
}
public abstract Node getAssets();
public abstract void updateCourse(List<CompoundMark> newCourse, List<Corner> sequence);
public abstract void updateBorder(List<Limit> border);
}
+175 -429
View File
@@ -1,242 +1,114 @@
package seng302.visualiser;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Timer;
import java.util.TimerTask;
import javafx.animation.AnimationTimer;
import javafx.application.Platform;
import javafx.geometry.Point2D;
import javafx.geometry.Point3D;
import javafx.scene.AmbientLight;
import javafx.scene.Camera;
import javafx.scene.Group;
import javafx.scene.Node;
import javafx.scene.PerspectiveCamera;
import javafx.scene.PointLight;
import javafx.scene.SceneAntialiasing;
import javafx.scene.SubScene;
import javafx.scene.effect.BlendMode;
import javafx.scene.image.ImageView;
import javafx.scene.input.KeyEvent;
import javafx.scene.paint.Color;
import javafx.scene.paint.PhongMaterial;
import javafx.scene.shape.Sphere;
import javafx.scene.text.Text;
import javafx.scene.transform.Rotate;
import javafx.scene.transform.Scale;
import javafx.scene.transform.Translate;
import seng302.gameServer.messages.RoundingSide;
import seng302.model.ClientYacht;
import seng302.model.GeoPoint;
import seng302.model.GameKeyBind;
import seng302.model.KeyAction;
import seng302.model.Limit;
import seng302.model.ScaledPoint;
import seng302.model.mark.CompoundMark;
import seng302.model.mark.Corner;
import seng302.model.mark.Mark;
import seng302.model.token.Token;
import seng302.utilities.GeoUtility;
import seng302.visualiser.fxObjects.assets_2D.BoatObject;
import seng302.utilities.Sounds;
import seng302.visualiser.cameras.ChaseCamera;
import seng302.visualiser.cameras.IsometricCamera;
import seng302.visualiser.cameras.RaceCamera;
import seng302.visualiser.cameras.TopDownCamera;
import seng302.visualiser.controllers.ViewManager;
import seng302.visualiser.fxObjects.MarkArrowFactory;
import seng302.visualiser.fxObjects.assets_3D.BoatObject;
import seng302.visualiser.fxObjects.assets_3D.Marker3D;
import seng302.visualiser.fxObjects.assets_3D.ModelFactory;
import seng302.visualiser.fxObjects.assets_3D.ModelType;
/**
* Collection of animated3D assets that displays a race.
*/
public class GameView3D {
public class GameView3D extends GameView{
private final double FOV = 60;
private final double DEFAULT_CAMERA_DEPTH = 100;
private final double DEFAULT_CAMERA_X = 0;
private final double DEFAULT_CAMERA_Y = 100;
private Group root3D;
private SubScene view;
// ParallelCamera camera;
private PerspectiveCamera camera;
private Group gameObjects;
private double bufferSize = 0;
private double canvasWidth = 200;
private double canvasHeight = 200;
private boolean horizontalInversion = false;
private double distanceScaleFactor;
private ScaleDirection scaleDirection;
private GeoPoint minLatPoint, minLonPoint, maxLatPoint, maxLonPoint;
private double referencePointX, referencePointY;
private double metersPerPixelX, metersPerPixelY;
final double SCALE_DELTA = 1.1;
private Text fpsDisplay = new Text();
private Group raceBorder = new Group();
// Cameras
private PerspectiveCamera isometricCam;
private PerspectiveCamera topDownCam;
private PerspectiveCamera chaseCam;
/* Note that if either of these is null then values for it have not been added and the other
should be used as the limits of the map. */
private List<Limit> borderPoints;
private Map<Mark, Group> markerObjects;
private Map<Mark, Marker3D> markerObjects;
private Map<ClientYacht, BoatObject> boatObjects = new HashMap<>();
private BoatObject selectedBoat = null;
private Group wakesGroup = new Group();
private Group boatObjectGroup = new Group();
private Group markers = new Group();
private Group tokens = new Group();
private Group playerAnnotation = new Group();
private List<CompoundMark> course = new ArrayList<>();
private List<Node> mapTokens;
private Timer playerBoatAnimationTimer = new Timer();
private AnimationTimer playerBoatAnimationTimer;
private Group trail = new Group();
private ImageView mapImage = new ImageView();
//FRAME RATE
private AnimationTimer timer;
private int NUM_SAMPLES = 10;
private final long[] frameTimes = new long[NUM_SAMPLES];
private Double frameRate = 60.0;
private int frameTimeIndex = 0;
private boolean arrayFilled = false;
private ClientYacht playerYacht;
private double windDir = 0.0;
double scaleFactor = 1;
private enum ScaleDirection {
HORIZONTAL,
VERTICAL
}
private Double windDir;
public GameView3D () {
camera = new PerspectiveCamera(true);
// camera = new ParallelCamera();
// gameObjects.getTransforms().add(new Scale(4,4,4));
// camera.setLayoutX(camera.getLayoutX()-400);
// camera.setLayoutY(camera.getLayoutY()-400);
camera.getTransforms().addAll(
new Translate(0,0, -DEFAULT_CAMERA_DEPTH)
);
camera.setFarClip(Double.MAX_VALUE);
camera.setNearClip(0.1);
camera.setFieldOfView(FOV);
isometricCam = new IsometricCamera(DEFAULT_CAMERA_X, DEFAULT_CAMERA_Y);
topDownCam = new TopDownCamera();
chaseCam = new ChaseCamera();
canvasWidth = canvasHeight = 300;
for (PerspectiveCamera pc : Arrays.asList(isometricCam, topDownCam, chaseCam)) {
pc.setFarClip(600);
pc.setNearClip(0.1);
pc.setFieldOfView(FOV);
}
gameObjects = new Group();
PointLight pl = new PointLight(Color.DARKGRAY);
pl.setLightOn(true);
pl.setBlendMode(BlendMode.ADD);
pl.setOpacity(0.5);
pl.getTransforms().add(new Translate(0,0,-500));
AmbientLight al = new AmbientLight();
al.setLightOn(true);
al.setBlendMode(BlendMode.SOFT_LIGHT);
al.getTransforms().add(new Translate(0, 0, -100));
root3D = new Group(camera, gameObjects);
root3D = new Group(isometricCam, gameObjects);
view = new SubScene(
root3D, 1000, 1000, true, SceneAntialiasing.BALANCED
);
view.setCamera(camera);
// view.setFill(Color.LIGHTBLUE);
camera.getTransforms().add(new Rotate(30, new Point3D(1,0,0)));
// gameObjects.getChildren().addAll(raceBorder, markers, tokens);
System.out.println(camera.getLayoutX());
System.out.println(camera.getTranslateX());
System.out.println(camera.getLayoutY());
System.out.println(camera.getTranslateY());
System.out.println(camera.getTranslateZ());
camera.setTranslateZ(-80);
camera.setTranslateY(170);
Sphere red = new Sphere(1);
red.setMaterial(new PhongMaterial(Color.RED));
red.setLayoutX(0);
red.setLayoutY(0);
Sphere blue = new Sphere(1);
blue.setMaterial(new PhongMaterial(Color.BLUE));
blue.setLayoutX(1);
blue.setLayoutY(0);
Sphere green = new Sphere(1);
green.setMaterial(new PhongMaterial(Color.GREEN));
green.setLayoutX(-.5);
green.setLayoutY(0);
Sphere white = new Sphere(1);
white.setMaterial(new PhongMaterial(Color.WHITE));
white.setLayoutX(-.25);
white.setLayoutY(0);
Sphere black = new Sphere(1);
black.setMaterial(new PhongMaterial(Color.BLACK));
black.setLayoutX(-.125);
black.setLayoutY(0);
view.setCamera(isometricCam);
gameObjects.getChildren().addAll(
// ModelFactory.importModel(ModelType.OCEAN).getAssets(),
raceBorder, trail, markers, tokens, playerAnnotation,
white, blue, green, black, red
ModelFactory.importModel(ModelType.OCEAN).getAssets(),
raceBorder, trail, markers, tokens
);
System.out.println(camera.getLayoutX());
System.out.println(camera.getTranslateX());
System.out.println(camera.getLayoutY());
System.out.println(camera.getTranslateY());
System.out.println(camera.getTranslateZ());
// Sphere s = new Sphere(1);
// s.setMaterial(new PhongMaterial(Color.RED));
// Sphere left = new Sphere(1);
// left.setMaterial(new PhongMaterial(Color.LEMONCHIFFON));
// left.getTransforms().add(new Translate(-Math.tan(Math.toRadians(FOV / 2)) * DEFAULT_CAMERA_DEPTH, 0, 0));
// Sphere right = new Sphere(1);
// right.setMaterial(new PhongMaterial(Color.ROSYBROWN));
// right.getTransforms().add(new Translate(Math.tan(Math.toRadians(FOV / 2)) * DEFAULT_CAMERA_DEPTH, 0, 0));
// Sphere top = new Sphere(1);
// top.setMaterial(new PhongMaterial(Color.TEAL));
// top.getTransforms().add(new Translate(0,-Math.tan(Math.toRadians(FOV / 2)) * DEFAULT_CAMERA_DEPTH, 0));
// Sphere bottom = new Sphere(1);
// bottom.setMaterial(new PhongMaterial(Color.BLANCHEDALMOND));
// bottom.getTransforms().add(new Translate(0, Math.tan(Math.toRadians(FOV / 2)) * DEFAULT_CAMERA_DEPTH, 0));
//
// Node boat = ModelFactory.boatGameView(BoatMeshType.DINGHY, Color.BLUE).getAssets();
// Node boat2 = ModelFactory.boatGameView(BoatMeshType.DINGHY, Color.BROWN).getAssets();
// boat2.getTransforms().add(new Translate(0,20, 0));
// Node boat3 = ModelFactory.boatGameView(BoatMeshType.DINGHY, Color.RED).getAssets();
// boat3.getTransforms().add(new Translate(0,-20, 0));
//
// Node sMarker = ModelFactory.importModel(ModelType.START_MARKER).getAssets();
// sMarker.getTransforms().add(0, new Translate(30, 30, 0));
//
// Node fMarker = ModelFactory.importModel(ModelType.FINISH_MARKER).getAssets();
// fMarker.getTransforms().add(0, new Translate(30, -30, 0));
//
// Node marker = ModelFactory.importModel(ModelType.PLAIN_MARKER).getAssets();
// marker.getTransforms().add(0, new Translate(30, 0, 0));
//
// Node coin = ModelFactory.importModel(ModelType.VELOCITY_PICKUP).getAssets();
// coin.setTranslateX(coin.getTranslateX() - 30);
//
// gameObjects.getChildren().addAll(
// ModelFactory.importModel(ModelType.OCEAN).getAssets(),
// s, left, right, top, bottom,
// boat, boat2, boat3,
// sMarker, fMarker, marker,
// coin
// );
view.sceneProperty().addListener((obs, old, scene) -> {
if (scene != null) {
scene.addEventHandler(KeyEvent.KEY_PRESSED, this::cameraMovement);
}
});
}
@Override
public void updateCourse(List<CompoundMark> newCourse, List<Corner> sequence) {
markerObjects = new HashMap<>();
compoundMarks = newCourse;
for (Corner corner : sequence) { //Makes course out of all compound marks.
for (CompoundMark compoundMark : newCourse) {
@@ -283,14 +155,17 @@ public class GameView3D {
}
}
createMarkArrows();
//Scale race to markers if there is no border.
if (borderPoints == null) {
rescaleRace(new ArrayList<>(markerObjects.keySet()));
scaledPoint = ScaledPoint.makeScaledPoint(
canvasWidth, canvasHeight, new ArrayList<>(markerObjects.keySet()), true
);
}
//Move the Markers to initial position.
markerObjects.forEach(((mark, marker) -> {
Point2D p2d = findScaledXY(mark.getLat(), mark.getLng());
System.out.println(mark.toString() + " " + p2d.toString());
Point2D p2d = scaledPoint.findScaledXY(mark.getLat(), mark.getLng());
marker.setLayoutX(p2d.getX());
marker.setLayoutY(p2d.getY());
}));
@@ -308,12 +183,9 @@ public class GameView3D {
* @param markerType the type of marker as a ModelType. Should be PLAIN_MARKER, START_MARKER or END_MARKER
*/
private void makeAndBindMarker(Mark observableMark, ModelType markerType) {
Group marker = ModelFactory.importModel(markerType).getAssets();
markerObjects.put(observableMark, marker);
markerObjects.put(observableMark, new Marker3D(markerType));
observableMark.addPositionListener((mark, lat, lon) -> {
Point2D p2d = findScaledXY(lat, lon);
Point2D p2d = scaledPoint.findScaledXY(lat, lon);
markerObjects.get(mark).setLayoutX(p2d.getX());
markerObjects.get(mark).setLayoutY(p2d.getY());
});
@@ -328,8 +200,8 @@ public class GameView3D {
* @return the new gate.
*/
private Group makeGate(Mark m1, Mark m2, ModelType gateType) {
Point2D m1Location = findScaledXY(m1);
Point2D m2Location = findScaledXY(m2);
Point2D m1Location = scaledPoint.findScaledXY(m1);
Point2D m2Location = scaledPoint.findScaledXY(m2);
Group barrier = ModelFactory.importModel(gateType).getAssets();
barrier.getTransforms().addAll(
@@ -350,190 +222,81 @@ public class GameView3D {
/**
* Sets the class variables minLatPoint, maxLatPoint, minLonPoint, maxLonPoint to the point with
* the leftmost point, rightmost point, southern most point and northern most point
* respectively.
* Calculates all the data needed for to create mark arrows. Requires that a course has been
* added to the gameview.
*/
private void findMinMaxPoint(List<GeoPoint> points) {
List<GeoPoint> sortedPoints = new ArrayList<>(points);
sortedPoints.sort(Comparator.comparingDouble(GeoPoint::getLat));
minLatPoint = new GeoPoint(sortedPoints.get(0).getLat(), sortedPoints.get(0).getLng());
GeoPoint maxLat = sortedPoints.get(sortedPoints.size() - 1);
maxLatPoint = new GeoPoint(maxLat.getLat(), maxLat.getLng());
sortedPoints.sort(Comparator.comparingDouble(GeoPoint::getLng));
minLonPoint = new GeoPoint(sortedPoints.get(0).getLat(), sortedPoints.get(0).getLng());
GeoPoint maxLon = sortedPoints.get(sortedPoints.size() - 1);
maxLonPoint = new GeoPoint(maxLon.getLat(), maxLon.getLng());
if (maxLonPoint.getLng() - minLonPoint.getLng() > 180) {
horizontalInversion = true;
private void createMarkArrows () {
for (int i=1; i < course.size()-1; i++) { //General case.
for (Mark mark : course.get(i).getMarks()) {
markerObjects.get(mark).addArrows(
mark.getRoundingSide() == RoundingSide.STARBOARD ? MarkArrowFactory.RoundingSide.STARBOARD : MarkArrowFactory.RoundingSide.PORT,
GeoUtility.getBearing(course.get(i-1).getMidPoint(), mark),
GeoUtility.getBearing(mark, course.get(i+1).getMidPoint())
);
}
}
createStartLineArrows();
createFinishLineArrows();
}
/**
* Calculates the location of a reference point, this is always the point with minimum latitude,
* in relation to the canvas.
*
* @param minLonToMaxLon The horizontal distance between the point of minimum longitude to
* maximum longitude.
*/
private void calculateReferencePointLocation(double minLonToMaxLon) {
GeoPoint referencePoint = minLatPoint;
double referenceAngle;
if (scaleDirection == ScaleDirection.HORIZONTAL) {
referenceAngle = Math.abs(
GeoUtility.getBearingRad(referencePoint, minLonPoint)
private void createStartLineArrows () {
for (Mark mark : course.get(0).getMarks()) {
markerObjects.get(mark).addArrows(
mark.getRoundingSide() == RoundingSide.STARBOARD ? MarkArrowFactory.RoundingSide.STARBOARD : MarkArrowFactory.RoundingSide.PORT,
0d, //90
GeoUtility.getBearing(mark, course.get(1).getMidPoint())
);
referencePointX =
-100 + distanceScaleFactor * Math.sin(referenceAngle) * GeoUtility
.getDistance(referencePoint, minLonPoint);
referenceAngle = Math.abs(GeoUtility.getDistance(referencePoint, maxLatPoint));
referencePointY = -100 + canvasHeight - (bufferSize + bufferSize);
referencePointY -= distanceScaleFactor * Math.cos(referenceAngle) * GeoUtility
.getDistance(referencePoint, maxLatPoint);
referencePointY = referencePointY / 2;
referencePointY += bufferSize;
referencePointY += distanceScaleFactor * Math.cos(referenceAngle) * GeoUtility
.getDistance(referencePoint, maxLatPoint);
} else {
referencePointY = -100 + canvasHeight - bufferSize;
referenceAngle = Math.abs(
Math.toRadians(
GeoUtility.getDistance(referencePoint, minLonPoint)
)
}
}
private void createFinishLineArrows () {
for (Mark mark : course.get(course.size()-1).getMarks()) {
markerObjects.get(mark).addArrows(
mark.getRoundingSide() == RoundingSide.STARBOARD ? MarkArrowFactory.RoundingSide.STARBOARD : MarkArrowFactory.RoundingSide.PORT,
GeoUtility.getBearing(course.get(course.size()-2).getMidPoint(), mark),
GeoUtility.getBearing(mark, mark)
);
referencePointX = -100 + bufferSize;
referencePointX += distanceScaleFactor * Math.sin(referenceAngle) * GeoUtility
.getDistance(referencePoint, minLonPoint);
referencePointX +=
((canvasWidth - (bufferSize + bufferSize)) - (minLonToMaxLon * distanceScaleFactor))
/ 2;
}
if (horizontalInversion) {
referencePointX = -100 + canvasWidth - bufferSize - (referencePointX - bufferSize);
}
}
/**
* Finds the scale factor necessary to fit all race markers within the onscreen map and assigns
* it to distanceScaleFactor Returns the max horizontal distance of the map.
*/
private double scaleRaceExtremities() {
double vertAngle = Math.abs(
GeoUtility.getBearingRad(minLatPoint, maxLatPoint)
);
double vertDistance =
Math.cos(vertAngle) * GeoUtility.getDistance(minLatPoint, maxLatPoint);
double horiAngle = Math.abs(
GeoUtility.getBearingRad(minLonPoint, maxLonPoint)
);
if (horiAngle <= (Math.PI / 2)) {
horiAngle = (Math.PI / 2) - horiAngle;
} else {
horiAngle = horiAngle - (Math.PI / 2);
}
double horiDistance =
Math.cos(horiAngle) * GeoUtility.getDistance(minLonPoint, maxLonPoint);
double vertScale = (canvasHeight - (bufferSize + bufferSize)) / vertDistance;
if ((horiDistance * vertScale) > (canvasWidth - (bufferSize + bufferSize))) {
distanceScaleFactor = (canvasWidth - (bufferSize + bufferSize)) / horiDistance;
scaleDirection = ScaleDirection.HORIZONTAL;
} else {
distanceScaleFactor = vertScale;
scaleDirection = ScaleDirection.VERTICAL;
}
return horiDistance;
}
private Point2D findScaledXY(GeoPoint unscaled) {
return findScaledXY(unscaled.getLat(), unscaled.getLng());
}
private Point2D findScaledXY(double unscaledLat, double unscaledLon) {
double distanceFromReference;
double angleFromReference;
double xAxisLocation = referencePointX;
double yAxisLocation = referencePointY;
angleFromReference = GeoUtility.getBearingRad(
minLatPoint, new GeoPoint(unscaledLat, unscaledLon)
);
distanceFromReference = GeoUtility.getDistance(
minLatPoint, new GeoPoint(unscaledLat, unscaledLon)
);
if (angleFromReference >= 0 && angleFromReference <= Math.PI / 2) {
xAxisLocation += distanceScaleFactor * Math.sin(angleFromReference) * distanceFromReference;
yAxisLocation -= distanceScaleFactor * Math.cos(angleFromReference) * distanceFromReference;
} else if (angleFromReference >= 0) {
angleFromReference = angleFromReference - Math.PI / 2;
xAxisLocation += distanceScaleFactor * Math.cos(angleFromReference) * distanceFromReference;
yAxisLocation += distanceScaleFactor * Math.sin(angleFromReference) * distanceFromReference;
} else if (angleFromReference < 0 && angleFromReference >= -Math.PI / 2) {
angleFromReference = Math.abs(angleFromReference);
xAxisLocation -= distanceScaleFactor * Math.sin(angleFromReference) * distanceFromReference;
yAxisLocation -= distanceScaleFactor * Math.cos(angleFromReference) * distanceFromReference;
} else {
angleFromReference = Math.abs(angleFromReference) - Math.PI / 2;
xAxisLocation -= distanceScaleFactor * Math.cos(angleFromReference) * distanceFromReference;
yAxisLocation += distanceScaleFactor * Math.sin(angleFromReference) * distanceFromReference;
}
if (horizontalInversion) {
xAxisLocation = canvasWidth - bufferSize - (xAxisLocation - bufferSize);
}
return new Point2D(xAxisLocation, yAxisLocation);
}
public void cameraMovement(KeyEvent event) {
switch (event.getCode()) {
case NUMPAD8:
camera.getTransforms().addAll(new Rotate(0.5, new Point3D(1,0,0)));
GameKeyBind keyBinds = GameKeyBind.getInstance();
KeyAction keyPressed = keyBinds.getKeyAction(event.getCode());
switch (keyPressed) {
case ZOOM_IN:
((RaceCamera) view.getCamera()).zoomIn();
break;
case NUMPAD2:
camera.getTransforms().addAll(new Rotate(-0.5, new Point3D(1,0,0)));
case ZOOM_OUT:
((RaceCamera) view.getCamera()).zoomOut();
break;
case NUMPAD4:
camera.getTransforms().addAll(new Rotate(-0.5, new Point3D(0,1,0)));
case FORWARD:
((RaceCamera) view.getCamera()).panUp();
break;
case NUMPAD6:
camera.getTransforms().addAll(new Rotate(0.5, new Point3D(0,1,0)));
case BACKWARD:
((RaceCamera) view.getCamera()).panDown();
break;
case X:
camera.getTransforms().addAll(new Translate(0, 0, 1.5));
case LEFT:
((RaceCamera) view.getCamera()).panLeft();
break;
case Z:
camera.getTransforms().addAll(new Translate(0, 0, -1.5));
case RIGHT:
((RaceCamera) view.getCamera()).panRight();
break;
case W:
camera.getTransforms().addAll(new Translate(0, -1, 0));
break;
case S:
camera.getTransforms().addAll(new Translate(0, 1, 0));
break;
case A:
camera.getTransforms().addAll(new Translate(-1, 0, 0));
break;
case D:
camera.getTransforms().addAll(new Translate(1, 0, 0));
case VIEW:
toggleCamera();
break;
}
}
/**
* Rescales the race to the size of the window.
*
* @param limitingCoordinates the set of geo points that contains the extremities of the race.
*/
private void rescaleRace(List<GeoPoint> limitingCoordinates) {
//Check is called once to avoid unnecessarily change the course limits once the race is running
findMinMaxPoint(limitingCoordinates);
double minLonToMaxLon = scaleRaceExtremities();
calculateReferencePointLocation(minLonToMaxLon);
// drawGoogleMap();
private void toggleCamera() {
Camera currCamera = view.getCamera();
if (currCamera.equals(isometricCam)) {
view.setCamera(topDownCam);
} else if (currCamera.equals(topDownCam)) {
view.setCamera(chaseCam);
} else {
view.setCamera(isometricCam);
}
}
/**
@@ -545,36 +308,27 @@ public class GameView3D {
final List<Group> wakes = new ArrayList<>();
for (ClientYacht clientYacht : yachts) {
Color colour = clientYacht.getColour();
newBoat = new BoatObject();
// newBoat.addSelectedBoatListener(this::setSelectedBoat);
newBoat = new BoatObject(clientYacht.getBoatType());
newBoat.setFill(colour);
boatObjects.put(clientYacht, newBoat);
// createAndBindAnnotationBox(clientYacht, colour);
wakesGroup.getChildren().add(newBoat.getWake());
wakes.add(newBoat.getWake());
boatObjectGroup.getChildren().add(newBoat);
// trails.getChildren().add(newBoat.getTrail());
clientYacht.addLocationListener((boat, lat, lon, heading, sailIn, velocity) -> {
BoatObject bo = boatObjects.get(boat);
Point2D p2d = findScaledXY(lat, lon);
Point2D p2d = scaledPoint.findScaledXY(lat, lon);
bo.moveTo(p2d.getX(), p2d.getY(), heading, velocity, sailIn, windDir);
// annotations.get(boat).setLocation(p2d.getX(), p2d.getY());
bo.setTrajectory(
heading,
velocity,
metersPerPixelX,
metersPerPixelY);
});
}
// annotationsGroup.getChildren().addAll(annotations.values());
Platform.runLater(() -> {
// gameObjects.addAll(trails);
if (clientYacht.getSourceId().equals(
ViewManager.getInstance().getGameClient().getServerThread().getClientId())) {
((ChaseCamera) chaseCam).setPlayerBoat(newBoat);
((TopDownCamera) topDownCam).setPlayerBoat(newBoat);
}
}
Platform.runLater(() -> {
gameObjects.getChildren().addAll(wakes);
gameObjects.getChildren().addAll(boatObjectGroup);
// gameObjects.addAll(annotationsGroup);
// gameObjects.addAll(boatObjectGroup);
});
}
@@ -591,18 +345,20 @@ public class GameView3D {
public void updateBorder(List<Limit> border) {
if (borderPoints == null) {
borderPoints = border;
rescaleRace(new ArrayList<>(borderPoints));
scaledPoint = ScaledPoint.makeScaledPoint(
canvasWidth, canvasHeight, new ArrayList<>(borderPoints), true
);
}
List<Node> boundaryAssets = new ArrayList<>();
Point2D lastLocation = findScaledXY(border.get(0).getLat(), border.get(0).getLng());
Point2D lastLocation = scaledPoint.findScaledXY(border.get(0).getLat(), border.get(0).getLng());
Group pylon = ModelFactory.importModel(ModelType.BORDER_PYLON).getAssets();
pylon.setLayoutX(lastLocation.getX());
pylon.setLayoutY(lastLocation.getY());
boundaryAssets.add(pylon);
for (int i=1; i<border.size(); i++) {
Point2D location = findScaledXY(border.get(i).getLat(), border.get(i).getLng());
Point2D location = scaledPoint.findScaledXY(border.get(i).getLat(), border.get(i).getLng());
pylon = ModelFactory.importModel(ModelType.BORDER_PYLON).getAssets();
pylon.setLayoutX(location.getX());
pylon.setLayoutY(location.getY());
@@ -615,7 +371,7 @@ public class GameView3D {
),
new Point3D(0,0,1)
),
new Scale((lastLocation.distance(location) / 15)-0.2, 1, 1)
new Scale((lastLocation.distance(location) / 10)-0.2, 1, 1)
);
Point2D midPoint = location.midpoint(lastLocation);
@@ -628,7 +384,7 @@ public class GameView3D {
boundaryAssets.add(pylon);
}
Point2D firstLocation = findScaledXY(border.get(0).getLat(), border.get(0).getLng());
Point2D firstLocation = scaledPoint.findScaledXY(border.get(0).getLat(), border.get(0).getLng());
Group barrier = ModelFactory.importModel(ModelType.BORDER_BARRIER).getAssets();
barrier.getTransforms().addAll(
new Rotate(
@@ -637,7 +393,7 @@ public class GameView3D {
),
new Point3D(0,0,1)
),
new Scale((firstLocation.distance(lastLocation) / 15)-0.2, 1, 1)
new Scale((firstLocation.distance(lastLocation) / 10)-0.2, 1, 1)
);
Point2D midPoint = lastLocation.midpoint(firstLocation);
@@ -656,7 +412,7 @@ public class GameView3D {
public void updateTokens(List<Token> newTokens) {
mapTokens = new ArrayList<>();
for (Token token : newTokens) {
Point2D location = findScaledXY(token.getLat(), token.getLng());
Point2D location = scaledPoint.findScaledXY(token.getLat(), token.getLng());
Node tokenObject = ModelFactory.importModel(ModelType.VELOCITY_PICKUP).getAssets();
tokenObject.setLayoutX(location.getX());
tokenObject.setLayoutY(location.getY());
@@ -669,74 +425,64 @@ public class GameView3D {
}
public void setBoatAsPlayer (ClientYacht playerYacht) {
this.playerYacht = playerYacht;
playerYacht.toggleSail();
playerBoatAnimationTimer = new AnimationTimer() {
Platform.runLater(() ->
playerAnnotation.getChildren().setAll(ModelFactory.importModel(ModelType.PLAYER_IDENTIFIER).getAssets())
);
BoatObject playerAssets = boatObjects.get(playerYacht);
playerAnnotation.layoutXProperty().bind(playerAssets.layoutXProperty());
playerAnnotation.layoutYProperty().bind(playerAssets.layoutYProperty());
playerBoatAnimationTimer.scheduleAtFixedRate(new TimerTask() {
private Point2D lastLocation = findScaledXY(playerYacht.getLocation());
Point2D lastLocation = scaledPoint.findScaledXY(playerYacht.getLocation());
@Override
public void run() {
Node segment = ModelFactory.importModel(ModelType.TRAIL_SEGMENT).getAssets();
Point2D location = findScaledXY(playerYacht.getLocation());
segment.getTransforms().addAll(
new Translate(location.getX(), location.getY()),
new Rotate(playerYacht.getHeading(), new Point3D(0,0,1)),
new Scale(1, lastLocation.distance(location) / 5)
);
Platform.runLater(() -> {
public void handle(long now) {
Point2D location = scaledPoint.findScaledXY(playerYacht.getLocation());
if (Math.abs(lastLocation.distance(location)) > 2) {
Node segment = ModelFactory.importModel(ModelType.TRAIL_SEGMENT).getAssets();
location = scaledPoint.findScaledXY(playerYacht.getLocation());
segment.getTransforms().addAll(
new Translate(location.getX(), location.getY(), 0),
new Rotate(playerYacht.getHeading(), new Point3D(0,0,1))
);
trail.getChildren().add(segment);
if (trail.getChildren().size() > 100) {
if (trail.getChildren().size() > 50) {
trail.getChildren().remove(0);
}
});
lastLocation = location;
// TODO: 11/09/2017 ROTATE PLAYER ICON
// double leg = playerYacht.getLegNumber();
// if (compoundMark != null) {
// for (Mark mark : compoundMark.getMarks()) {
//// System.out.println("markerObjects.get(mark) = " + markerObjects.get(mark));
// markerObjects.get(mark).showNextExitArrow();
// }
// }
// CompoundMark nextMark = null;
// if (legNumber < course.size() - 1) {
// nextMark = course.get(legNumber);
// for (Mark mark : nextMark.getMarks()) {
// markerObjects.get(mark).showNextEnterArrow();
// }
// }
lastLocation = location;
}
}
}, 0L, 500L);
// playerYacht.toggleSail();
// boatObjects.get(playerYacht).setAsPlayer();
// CompoundMark currentMark = course.get(playerYacht.getLegNumber());
// for (Mark mark : currentMark.getMarks()) {
// markerObjects.get(mark).showNextExitArrow();
// }
// annotations.get(playerYacht).addAnnotation(
// "velocity",
// playerYacht.getVelocityProperty(),
// (velocity) -> String.format("Speed: %.2f ms", velocity.doubleValue())
// );
// Platform.runLater(() -> {
// boatObjectGroup.getChildren().remove(boatObjects.get(playerYacht));
// gameObjects.add(boatObjects.get(playerYacht));
// annotationsGroup.getChildren().remove(annotations.get(playerYacht));
// gameObjects.add(annotations.get(playerYacht));
// });
// playerYacht.addMarkRoundingListener(this::updateMarkArrows);
};
playerBoatAnimationTimer.start();
playerYacht.addMarkRoundingListener(this::updateMarkArrows);
boatObjects.get(playerYacht).addSelectedBoatListener((boatObject, isSelected) -> {
System.out.println("IS SELECTED " + isSelected);
});
}
public void setWindDir(double windDir) {
this.windDir = windDir;
}
private void updateMarkArrows (ClientYacht yacht, int legNumber) {
CompoundMark compoundMark;
if (legNumber - 1 >= 0) {
Sounds.playMarkRoundingSound();
compoundMark = course.get(legNumber-1);
for (Mark mark : compoundMark.getMarks()) {
markerObjects.get(mark).showNextExitArrow();
}
}
CompoundMark nextMark = null;
if (legNumber < course.size() - 1) {
Sounds.playMarkRoundingSound();
nextMark = course.get(legNumber);
for (Mark mark : nextMark.getMarks()) {
markerObjects.get(mark).showNextEnterArrow();
}
}
if (legNumber - 2 >= 0) {
CompoundMark lastMark = course.get(Math.max(0, legNumber - 2));
if (lastMark != nextMark) {
for (Mark mark : lastMark.getMarks()) {
markerObjects.get(mark).hideAllArrows();
}
}
}
}
}
@@ -0,0 +1,120 @@
package seng302.visualiser;
import java.io.File;
import java.io.IOException;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.List;
import javafx.scene.Node;
import javafx.util.Pair;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import org.w3c.dom.Document;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import seng302.model.stream.xml.generator.RaceXMLTemplate;
import seng302.model.stream.xml.generator.RegattaXMLTemplate;
import seng302.model.stream.xml.parser.RaceXMLData;
import seng302.model.stream.xml.parser.RegattaXMLData;
import seng302.utilities.XMLGenerator;
import seng302.utilities.XMLParser;
/**
* Makes maps from map definition xml files.
*/
public class MapMaker {
private List<MapPreview> mapPreviews = new ArrayList<>();
private List<RaceXMLData> races = new ArrayList<>();
private List<RegattaXMLData> regattas = new ArrayList<>();
private List<String> filePaths = new ArrayList<>();
private List<Integer> maxPlayers = new ArrayList<>();
private static MapMaker instance;
private int index = 0;
private XMLGenerator xmlGenerator = new XMLGenerator();
public static MapMaker getInstance() {
if (instance == null) {
instance = new MapMaker();
}
return instance;
}
private MapMaker() {
File dir = new File(MapMaker.class.getResource("/maps/").getPath());
File[] directoryListing = dir.listFiles();
if (directoryListing != null) {
for (File child : directoryListing) {
Pair<RegattaXMLTemplate, RaceXMLTemplate> regattaRace = XMLParser.parseRaceDef(
child.getAbsolutePath(), "", 1, null, false
);
filePaths.add(child.getAbsolutePath());
RegattaXMLTemplate regattaTemplate = regattaRace.getKey();
regattas.add(new RegattaXMLData(
regattaTemplate.getRegattaId(),
regattaTemplate.getName(),
regattaTemplate.getCourseName(),
regattaTemplate.getLatitude(),
regattaTemplate.getLongitude(),
regattaTemplate.getUtcOffset()
));
RaceXMLTemplate raceTemplate = regattaRace.getValue();
xmlGenerator.setRaceTemplate(raceTemplate);
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
DocumentBuilder db;
Document doc = null;
try {
db = dbf.newDocumentBuilder();
doc = db.parse(new InputSource(new StringReader(xmlGenerator.getRaceAsXml())));
} catch (ParserConfigurationException | IOException | SAXException e) {
e.printStackTrace();
}
RaceXMLData race = XMLParser.parseRace(doc);
maxPlayers.add(XMLParser.getMaxPlayers(doc));
mapPreviews.add(new MapPreview(
new ArrayList<>(race.getCompoundMarks().values()),
race.getMarkSequence(), race.getCourseLimit()
));
races.add(race);
}
}
}
public void next() {
index += 1;
if (index >= mapPreviews.size()) {
index = 0;
}
}
public void previous() {
index -= 1;
if (index < 0) {
index = mapPreviews.size() - 1;
}
}
public Node getCurrentGameView() {
return mapPreviews.get(index).getAssets();
}
public RaceXMLData getCurrentRace() {
return races.get(index);
}
public RegattaXMLData getCurrentRegatta() {
return regattas.get(index);
}
public String getCurrentRacePath() {
return filePaths.get(index);
}
public Integer getMaxPlayers() {
return maxPlayers.get(index);
}
}
@@ -0,0 +1,283 @@
package seng302.visualiser;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javafx.application.Platform;
import javafx.geometry.Point2D;
import javafx.scene.Node;
import javafx.scene.paint.Color;
import javafx.scene.paint.Paint;
import javafx.scene.shape.Polygon;
import seng302.gameServer.messages.RoundingSide;
import seng302.model.GeoPoint;
import seng302.model.Limit;
import seng302.model.ScaledPoint;
import seng302.model.mark.CompoundMark;
import seng302.model.mark.Corner;
import seng302.model.mark.Mark;
import seng302.utilities.GeoUtility;
import seng302.visualiser.fxObjects.MarkArrowFactory;
import seng302.visualiser.fxObjects.assets_2D.CourseBoundary;
import seng302.visualiser.fxObjects.assets_2D.Gate;
import seng302.visualiser.fxObjects.assets_2D.Marker2D;
/**
* Created by cir27 on 20/07/17.
*/
public class MapPreview extends GameView {
private Polygon raceBorder = new CourseBoundary();
private Map<Mark, Marker2D> markerObjects;
public MapPreview(List<CompoundMark> marks, List<Corner> course, List<Limit> border) {
this.compoundMarks = marks;
this.courseOrder = course;
this.borderPoints = border;
gameObjects.getChildren().addAll(raceBorder, markers, tokens);
gameObjects.parentProperty().addListener((obs, old, parent) -> {
if (parent != null) {
canvasWidth = parent.prefWidth(1);
canvasHeight = parent.prefHeight(1);
updateBorder(borderPoints);
updateCourse(compoundMarks, courseOrder);
}
});
}
@Override
public Node getAssets() {
return gameObjects;
}
public void setSize(double width, double height) {
canvasHeight = height;
canvasWidth = width;
updateBorder(borderPoints);
updateCourse(compoundMarks, courseOrder);
}
/**
* Adds a course to the GameView. The view is scaled accordingly unless a border is set in which
* case the course is added relative ot the border.
*
* @param newCourse the mark objects that make up the course.
* @param sequence The sequence the marks travel through
*/
@Override
public void updateCourse(List<CompoundMark> newCourse, List<Corner> sequence) {
if (newCourse.size() == 0) {
return;
}
compoundMarks = newCourse;
markerObjects = new HashMap<>();
courseOrder = sequence;
for (Corner corner : courseOrder) { //Makes course out of all compound marks.
for (CompoundMark compoundMark : newCourse) {
if (corner.getCompoundMarkID() == compoundMark.getId()) {
course.add(compoundMark);
}
}
}
// TODO: 16/08/17 Updating mark roundings here. It should not happen here. Nor should it be done this way.
for (Corner corner : sequence){
CompoundMark compoundMark = course.get(corner.getSeqID() - 1);
compoundMark.setRoundingSide(
RoundingSide.getRoundingSide(corner.getRounding())
);
}
final List<Gate> gates = new ArrayList<>();
Paint colour = Color.BLACK;
//Creates new markers
for (CompoundMark cMark : newCourse) {
//Set start and end colour
if (cMark.getId() == sequence.get(0).getCompoundMarkID()) {
colour = Color.GREEN;
} else if (cMark.getId() == sequence.get(sequence.size() - 1).getCompoundMarkID()) {
colour = Color.RED;
}
//Create mark dots
for (Mark mark : cMark.getMarks()) {
makeAndBindMarker(mark, colour);
}
//Create gate line
if (cMark.isGate()) {
for (int i = 1; i < cMark.getMarks().size(); i++) {
gates.add(
makeAndBindGate(
markerObjects.get(cMark.getSubMark(i)),
markerObjects.get(cMark.getSubMark(i + 1)),
colour
)
);
}
}
colour = Color.BLACK;
}
createMarkArrows(sequence);
//Scale race to markers if there is no border.
if (borderPoints == null) {
scaledPoint = ScaledPoint.makeScaledPoint(
canvasWidth, canvasHeight, new ArrayList<>(markerObjects.keySet()), false
);
}
//Move the Markers to initial position.
markerObjects.forEach(((mark, marker2D) -> {
Point2D p2d = scaledPoint.findScaledXY(mark.getLat(), mark.getLng());
marker2D.setLayoutX(p2d.getX());
marker2D.setLayoutY(p2d.getY());
}));
Platform.runLater(() -> {
markers.getChildren().clear();
markers.getChildren().addAll(gates);
markers.getChildren().addAll(markerObjects.values());
});
}
/**
* Calculates all the data needed for to create mark arrows. Requires that a course has been
* added to the gameview.
* @param sequence The order in which marks are traversed.
*/
private void createMarkArrows (List<Corner> sequence) {
for (int i=1; i < sequence.size()-1; i++) { //General case.
double averageLat = 0;
double averageLng = 0;
int numMarks = course.get(i-1).getMarks().size();
for (Mark mark : course.get(i-1).getMarks()) {
averageLat += mark.getLat();
averageLng += mark.getLng();
}
GeoPoint lastMarkAv = new GeoPoint(averageLat / numMarks, averageLng / numMarks);
numMarks = course.get(i+1).getMarks().size();
averageLat = 0;
averageLng = 0;
for (Mark mark : course.get(i+1).getMarks()) {
averageLat += mark.getLat();
averageLng += mark.getLng();
}
GeoPoint nextMarkAv = new GeoPoint(averageLat / numMarks, averageLng / numMarks);
// TODO: 16/08/17 This comparison doesn't need to exist but the alternative is to user server enum client side.
for (Mark mark : course.get(i).getMarks()) {
markerObjects.get(mark).addArrows(
mark.getRoundingSide() == RoundingSide.STARBOARD ? MarkArrowFactory.RoundingSide.STARBOARD : MarkArrowFactory.RoundingSide.PORT,
GeoUtility.getBearing(lastMarkAv, mark),
GeoUtility.getBearing(mark, nextMarkAv)
);
}
}
createStartLineArrows();
createFinishLineArrows();
}
private void createStartLineArrows () {
double averageLat = 0;
double averageLng = 0;
int numMarks = 0;
for (Mark mark : course.get(1).getMarks()) {
numMarks += 1;
averageLat += mark.getLat();
averageLng += mark.getLng();
}
GeoPoint firstMarkAv = new GeoPoint(averageLat / numMarks, averageLng / numMarks);
for (Mark mark : course.get(0).getMarks()) {
markerObjects.get(mark).addArrows(
mark.getRoundingSide() == RoundingSide.STARBOARD ? MarkArrowFactory.RoundingSide.STARBOARD : MarkArrowFactory.RoundingSide.PORT,
0d, //90
GeoUtility.getBearing(mark, firstMarkAv)
);
}
}
private void createFinishLineArrows () {
double numMarks = 0;
double averageLat = 0;
double averageLng = 0;
for (Mark mark : course.get(course.size()-2).getMarks()) {
numMarks += 1;
averageLat += mark.getLat();
averageLng += mark.getLng();
}
GeoPoint secondToLastMarkAv = new GeoPoint(averageLat / numMarks, averageLng / numMarks);
for (Mark mark : course.get(course.size()-1).getMarks()) {
markerObjects.get(mark).addArrows(
mark.getRoundingSide() == RoundingSide.STARBOARD ? MarkArrowFactory.RoundingSide.STARBOARD : MarkArrowFactory.RoundingSide.PORT,
GeoUtility.getBearing(secondToLastMarkAv, mark),
GeoUtility.getBearing(mark, mark)
);
}
}
/**
* Creates a new Marker and binds it's position to the given Mark.
*
* @param observableMark The mark to bind the marker to.
* @param colour The desired colour of the mark
*/
private void makeAndBindMarker(Mark observableMark, Paint colour) {
Marker2D marker2D = new Marker2D(colour);
// marker.addArrows(MarkArrowFactory.RoundingSide.PORT, ThreadLocalRandom.current().nextDouble(91, 180), ThreadLocalRandom.current().nextDouble(1, 90));
markerObjects.put(observableMark, marker2D);
observableMark.addPositionListener((mark, lat, lon) -> {
Point2D p2d = scaledPoint.findScaledXY(lat, lon);
markerObjects.get(mark).setLayoutX(p2d.getX());
markerObjects.get(mark).setLayoutY(p2d.getY());
});
}
/**
* Creates a new gate connecting the given marks.
*
* @param m1 The first Mark of the gate.
* @param m2 The second Mark of the gate.
* @param colour The desired colour of the gate.
* @return the new gate.
*/
private Gate makeAndBindGate(Marker2D m1, Marker2D m2, Paint colour) {
Gate gate = new Gate(colour);
gate.startXProperty().bind(
m1.layoutXProperty()
);
gate.startYProperty().bind(
m1.layoutYProperty()
);
gate.endXProperty().bind(
m2.layoutXProperty()
);
gate.endYProperty().bind(
m2.layoutYProperty()
);
return gate;
}
/**
* Adds a border to the GameView and rescales to the size of the border, does not rescale if a
* border already exists. Assumes the border is larger than the course.
*
* @param border the race border to be drawn.
*/
@Override
public void updateBorder(List<Limit> border) {
if (border.size() == 0) {
return;
}
borderPoints = border;
scaledPoint = ScaledPoint.makeScaledPoint(canvasWidth, canvasHeight, border, false);
List<Double> boundaryPoints = new ArrayList<>();
for (Limit limit : border) {
Point2D location = scaledPoint.findScaledXY(limit.getLat(), limit.getLng());
boundaryPoints.add(location.getX());
boundaryPoints.add(location.getY());
}
raceBorder.getPoints().setAll(boundaryPoints);
}
}
@@ -0,0 +1,130 @@
package seng302.visualiser.cameras;
import java.util.Arrays;
import javafx.beans.property.DoubleProperty;
import javafx.collections.ObservableList;
import javafx.geometry.Point3D;
import javafx.scene.PerspectiveCamera;
import javafx.scene.transform.Rotate;
import javafx.scene.transform.Transform;
import javafx.scene.transform.Translate;
import seng302.visualiser.fxObjects.assets_3D.BoatObject;
public class ChaseCamera extends PerspectiveCamera implements RaceCamera {
private final Double VERTICAL_PAN_LIMIT = 20.0;
private final Double NEAR_ZOOM_LIMIT = -15.0;
private final Double FAR_ZOOM_LIMIT = -125.0;
private final Double ZOOM_STEP = 2.5;
private final Double PAN_STEP = 2.5;
private ObservableList<Transform> transforms;
private BoatObject playerBoat;
private Double zoomFactor;
private Double horizontalPan;
private Double verticalPan;
public ChaseCamera() {
super(true);
transforms = this.getTransforms();
zoomFactor = (FAR_ZOOM_LIMIT + NEAR_ZOOM_LIMIT) / 2.0;
this.horizontalPan = 0.0;
this.verticalPan = 0.0;
}
/**
* Sets a player boat object to observe and update the camera with.
*
* @param playerBoat The player boat to be observed.
*/
public void setPlayerBoat(BoatObject playerBoat) {
this.playerBoat = playerBoat;
for (DoubleProperty o : Arrays
.asList(playerBoat.getRotationProperty(), playerBoat.layoutYProperty(),
playerBoat.layoutXProperty())) {
o.addListener((obs, oldVal, newVal) -> repositionCamera());
}
}
/**
* Moves the camera to a new position after some change (Zooming or Panning)
*/
private void repositionCamera() {
transforms.clear();
transforms.addAll(
new Translate(playerBoat.getLayoutX(), playerBoat.getLayoutY(), 0),
new Rotate(playerBoat.getRotationProperty().getValue() + horizontalPan,
new Point3D(0, 0, 1)),
new Rotate(60 + verticalPan, new Point3D(1, 0, 0)),
new Translate(0, 0, zoomFactor)
);
}
/**
* Adjusts the zoom amount (camera depth) by some adjustment value
* @param adjustment the adjustment to be made to the camera
*/
private void adjustZoomFactor(Double adjustment) {
if (zoomFactor + adjustment < NEAR_ZOOM_LIMIT && zoomFactor + adjustment > FAR_ZOOM_LIMIT) {
zoomFactor = zoomFactor + adjustment;
repositionCamera();
}
}
/**
* Adjusts the Vertical Panning of the Camera
* @param adjustment the adjustment to be made to the camera
*/
private void adjustVerticalPan(Double adjustment) {
if (verticalPan + adjustment >= -VERTICAL_PAN_LIMIT
&& verticalPan + adjustment <= VERTICAL_PAN_LIMIT) {
verticalPan += adjustment;
repositionCamera();
}
}
/**
* Adjusts the Horizontal Panning of the Camera.
* @param adjustment the adjustment to be made to the camera
*/
private void adjustHorizontalPan(Double adjustment) {
this.horizontalPan += adjustment;
repositionCamera();
}
@Override
public void zoomIn() {
adjustZoomFactor(ZOOM_STEP);
}
@Override
public void zoomOut() {
adjustZoomFactor(-ZOOM_STEP);
}
@Override
public void panLeft() {
adjustHorizontalPan(-PAN_STEP);
}
@Override
public void panRight() {
adjustHorizontalPan(PAN_STEP);
}
@Override
public void panUp() {
adjustVerticalPan(-PAN_STEP);
}
@Override
public void panDown() {
adjustVerticalPan(PAN_STEP);
}
}
@@ -0,0 +1,113 @@
package seng302.visualiser.cameras;
import javafx.collections.ObservableList;
import javafx.geometry.Point3D;
import javafx.scene.PerspectiveCamera;
import javafx.scene.transform.Rotate;
import javafx.scene.transform.Transform;
import javafx.scene.transform.Translate;
public class IsometricCamera extends PerspectiveCamera implements RaceCamera {
private final Double MIN_X = -120.0;
private final Double MAX_X = 125.0;
private final Double MIN_Y = 40.0;
private final Double MAX_Y = 170.0;
private final Double PAN_LIMIT = 160.0;
private final Double NEAR_ZOOM_LIMIT = -50.0;
private final Double FAR_ZOOM_LIMIT = -160.0;
private Double horizontalPan;
private Double verticalPan;
private Double zoomFactor;
private ObservableList<Transform> transforms;
public IsometricCamera(Double cameraStartX, Double cameraStartY) {
super(true);
transforms = this.getTransforms();
zoomFactor = (FAR_ZOOM_LIMIT + NEAR_ZOOM_LIMIT) / 2.0;
horizontalPan = cameraStartX;
verticalPan = cameraStartY;
updateCamera();
}
/**
* Moves the camera to a new position after some change (Zooming or Panning)
*/
private void updateCamera() {
transforms.clear();
transforms.addAll(
new Translate(horizontalPan, verticalPan, zoomFactor),
new Rotate(30, new Point3D(1, 0, 0))
);
}
/**
* Adjusts the zoom amount (camera depth) by some adjustment value
*
* @param adjustment the adjustment to be made to the camera
*/
private void adjustZoomFactor(Double adjustment) {
if (zoomFactor + adjustment < NEAR_ZOOM_LIMIT && zoomFactor + adjustment > FAR_ZOOM_LIMIT) {
zoomFactor = zoomFactor + adjustment;
updateCamera();
}
}
/**
* Adjusts the Vertical Panning of the Camera
* @param adjustment the adjustment to be made to the camera
*/
private void adjustVerticalPan(Double adjustment) {
if (verticalPan + adjustment >= MIN_Y && verticalPan + adjustment <= MAX_Y) {
verticalPan += adjustment;
updateCamera();
}
}
/**
* Adjusts the Horizontal Panning of the Camera.
* @param adjustment the adjustment to be made to the camera
*/
private void adjustHorizontalPan(Double adjustment) {
if (horizontalPan + adjustment >= MIN_X && horizontalPan + adjustment <= MIN_Y) {
this.horizontalPan += adjustment;
updateCamera();
}
}
@Override
public void zoomIn() {
adjustZoomFactor(-2.5);
}
@Override
public void zoomOut() {
adjustZoomFactor(2.5);
}
@Override
public void panLeft() {
adjustHorizontalPan(-2.5);
}
@Override
public void panRight() {
adjustHorizontalPan(2.5);
}
@Override
public void panUp() {
adjustVerticalPan(-2.5);
}
@Override
public void panDown() {
adjustVerticalPan(2.5);
}
}
@@ -0,0 +1,18 @@
package seng302.visualiser.cameras;
public interface RaceCamera {
void zoomIn();
void zoomOut();
void panLeft();
void panRight();
void panUp();
void panDown();
}
@@ -0,0 +1,123 @@
package seng302.visualiser.cameras;
import java.util.Arrays;
import javafx.beans.property.DoubleProperty;
import javafx.collections.ObservableList;
import javafx.scene.PerspectiveCamera;
import javafx.scene.transform.Transform;
import javafx.scene.transform.Translate;
import seng302.visualiser.fxObjects.assets_3D.BoatObject;
public class TopDownCamera extends PerspectiveCamera implements RaceCamera {
private final Double PAN_LIMIT = 30.0;
private final Double NEAR_ZOOM_LIMIT = -30.0;
private final Double FAR_ZOOM_LIMIT = -130.0;
private final Double ZOOM_STEP = 2.5;
private ObservableList<Transform> transforms;
private BoatObject playerBoat;
private Double zoomFactor;
private Double horizontalPan;
private Double verticalPan;
public TopDownCamera() {
super(true);
transforms = this.getTransforms();
zoomFactor = (FAR_ZOOM_LIMIT + NEAR_ZOOM_LIMIT) / 2.0;
horizontalPan = 0.0;
verticalPan = 0.0;
}
/**
* Sets a player boat object to observe and update the camera with.
*
* @param playerBoat The player boat to be observed.
*/
public void setPlayerBoat(BoatObject playerBoat) {
this.playerBoat = playerBoat;
for (DoubleProperty o : Arrays
.asList(playerBoat.layoutXProperty(), playerBoat.layoutYProperty())) {
o.addListener((obs, oldVal, newVal) -> updateCamera());
}
}
/**
* Moves the camera to a new position after some change (Zooming or Panning)
*/
private void updateCamera() {
transforms.clear();
transforms.addAll(
new Translate(playerBoat.getLayoutX() + horizontalPan,
playerBoat.getLayoutY() + verticalPan, zoomFactor)
);
}
/**
* Adjusts the zoom amount (camera depth) by some adjustment value
* @param adjustment the adjustment to be made to the camera
*/
private void adjustZoomFactor(Double adjustment) {
if (zoomFactor + adjustment < NEAR_ZOOM_LIMIT && zoomFactor + adjustment > FAR_ZOOM_LIMIT) {
zoomFactor = zoomFactor + adjustment;
updateCamera();
}
}
/**
* Adjusts the Vertical Panning of the Camera
* @param adjustment the adjustment to be made to the camera
*/
private void adjustVerticalPan(Double adjustment) {
if (verticalPan + adjustment >= -PAN_LIMIT && verticalPan + adjustment <= PAN_LIMIT) {
verticalPan += adjustment;
updateCamera();
}
}
/**
* Adjusts the Horizontal Panning of the Camera.
* @param adjustment the adjustment to be made to the camera
*/
private void adjustHorizontalPan(Double adjustment) {
if (horizontalPan + adjustment >= -PAN_LIMIT && horizontalPan + adjustment <= PAN_LIMIT) {
horizontalPan += adjustment;
updateCamera();
}
}
@Override
public void zoomIn() {
adjustZoomFactor(ZOOM_STEP);
}
@Override
public void zoomOut() {
adjustZoomFactor(-ZOOM_STEP);
}
@Override
public void panLeft() {
adjustHorizontalPan(-1.0);
}
@Override
public void panRight() {
adjustHorizontalPan(1.0);
}
@Override
public void panUp() {
adjustVerticalPan(-1.0);
}
@Override
public void panDown() {
adjustVerticalPan(1.0);
}
}
@@ -42,8 +42,8 @@ public class FinishScreenViewController implements Initializable {
@Override
public void initialize(URL location, ResourceBundle resources) {
finishScreenGridPane.getStylesheets()
.add(getClass().getResource("/css/master.css").toString());
finishOrderTable.getStylesheets().add(getClass().getResource("/css/master.css").toString());
.add(getClass().getResource("/css/Master.css").toString());
finishOrderTable.getStylesheets().add(getClass().getResource("/css/Master.css").toString());
// set up data for table
finishOrderTable.setItems(data);
@@ -88,7 +88,6 @@ public class FinishScreenViewController implements Initializable {
public void switchToStartScreenView() {
Sounds.playButtonClick();
//TODO merge fix
setContentPane("/views/StartScreenView.fxml");
}
@@ -15,7 +15,7 @@ import javafx.fxml.FXMLLoader;
import javafx.fxml.Initializable;
import javafx.scene.control.Label;
import javafx.scene.control.ScrollPane;
import javafx.scene.layout.Pane;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
@@ -29,12 +29,15 @@ import seng302.model.mark.CompoundMark;
import seng302.model.mark.Corner;
import seng302.model.stream.xml.parser.RaceXMLData;
import seng302.utilities.Sounds;
import seng302.visualiser.GameView;
import seng302.visualiser.MapPreview;
import seng302.visualiser.controllers.cells.PlayerCell;
import seng302.visualiser.controllers.dialogs.BoatCustomizeController;
public class LobbyController implements Initializable {
private final double INITIAL_MAP_HEIGHT = 770d;
private final double INITIAL_MAP_WIDTH = 574d;
//--------FXML BEGIN--------//
@FXML
private VBox playerListVBox;
@@ -49,15 +52,15 @@ public class LobbyController implements Initializable {
@FXML
private Label mapName;
@FXML
private Pane serverMap;
private AnchorPane serverMap;
//---------FXML END---------//
private RaceState raceState;
private JFXDialog customizationDialog;
public Color playersColor;
private Map<Integer, ClientYacht> playerBoats;
private Double mapWidth, mapHeight;
private GameView gameView;
private Double mapWidth = INITIAL_MAP_WIDTH, mapHeight = INITIAL_MAP_HEIGHT;
private MapPreview mapPreview;
@Override
public void initialize(URL location, ResourceBundle resources) {
@@ -89,17 +92,16 @@ public class LobbyController implements Initializable {
ViewManager.getInstance().getPlayerList().setAll(ViewManager.getInstance().getPlayerList().sorted());
});
customizeButton.setOnMouseReleased(event -> {
customizationDialog = createCustomizeDialog();
Sounds.playButtonClick();
customizationDialog.show();
});
Platform.runLater(() -> {
Integer playerId = ViewManager.getInstance().getGameClient().getServerThread().getClientId();
String name = ViewManager.getInstance().getGameClient().getPlayerNames().get(playerId - 1);
playersColor = Colors.getColor(playerId - 1);
customizationDialog = createCustomizeDialog();
customizeButton.setOnMouseReleased(event -> {
Sounds.playButtonClick();
customizationDialog.show();
});
});
leaveLobbyButton.setOnMouseEntered(e -> Sounds.playHoverSound());
@@ -110,7 +112,6 @@ public class LobbyController implements Initializable {
}
private JFXDialog createCustomizeDialog() {
// TODO: 12/09/17 ajm412: Why is this here? is there no better way we can do this? Ideally inside the LobbyController.
FXMLLoader dialog = new FXMLLoader(
getClass().getResource("/views/dialogs/BoatCustomizeDialog.fxml"));
@@ -119,7 +120,6 @@ public class LobbyController implements Initializable {
try {
customizationDialog = new JFXDialog(serverListMainStackPane, dialog.load(),
JFXDialog.DialogTransition.CENTER);
} catch (IOException e) {
e.printStackTrace();
}
@@ -128,48 +128,38 @@ public class LobbyController implements Initializable {
controller.setParentController(this);
controller.setPlayerColor(this.playersColor);
controller.setPlayerName(this.playerBoats
.get(ViewManager.getInstance().getGameClient().getServerThread().getClientId())
.getBoatName());
controller.setCurrentBoat(this.playerBoats.get(ViewManager.getInstance().getGameClient().getServerThread().getClientId())
.getBoatType().toString());
return customizationDialog;
}
/**
*
*/
private void refreshMapView(){
RaceXMLData raceData = ViewManager.getInstance().getGameClient().getCourseData();
List<Limit> border = raceData.getCourseLimit();
List<CompoundMark> marks = new ArrayList<CompoundMark>(raceData.getCompoundMarks().values());
List<Corner> corners = raceData.getMarkSequence();
gameView.setSize(mapWidth, mapHeight);
// Update game view
gameView.updateBorder(border);
gameView.updateCourse(marks, corners);
}
/**
* Initializes a top down preview of the race course map.
*/
private void initMapPreview() {
gameView = new GameView();
gameView.setHorizontalBuffer(330d);
RaceXMLData raceData = ViewManager.getInstance().getGameClient().getCourseData();
List<Limit> border = raceData.getCourseLimit();
List<CompoundMark> marks = new ArrayList<>(raceData.getCompoundMarks().values());
List<Corner> corners = raceData.getMarkSequence();
mapWidth = 770d;
mapHeight = 574d;
// Add game view
mapPreview = new MapPreview(marks, corners, border);
serverMap.getChildren().clear();
serverMap.getChildren().add(gameView);
serverMap.getChildren().add(mapPreview.getAssets());
mapPreview.setSize(mapWidth, mapHeight);
serverMap.widthProperty().addListener((observable, oldValue, newValue) -> {
mapWidth = newValue.doubleValue();
refreshMapView();
mapPreview.setSize(mapWidth, mapHeight);
});
//
serverMap.heightProperty().addListener((observable, oldValue, newValue) -> {
mapHeight = newValue.doubleValue();
refreshMapView();
mapPreview.setSize(mapWidth, mapHeight);
});
}
@@ -201,7 +191,7 @@ public class LobbyController implements Initializable {
FXMLLoader loader = new FXMLLoader(
getClass().getResource("/views/cells/PlayerCell.fxml"));
loader.setController(new PlayerCell(playerId, yacht.getBoatName(), yacht.getColour()));
loader.setController(new PlayerCell(playerId, yacht));
try {
pane = loader.load();
@@ -1,6 +1,7 @@
package seng302.visualiser.controllers;
import com.jfoenix.controls.JFXButton;
import com.jfoenix.controls.JFXDialog;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
@@ -11,8 +12,6 @@ import java.util.concurrent.TimeUnit;
import javafx.animation.Timeline;
import javafx.application.Platform;
import javafx.beans.property.ReadOnlyBooleanProperty;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
@@ -23,8 +22,6 @@ import javafx.scene.Scene;
import javafx.scene.SubScene;
import javafx.scene.chart.LineChart;
import javafx.scene.chart.NumberAxis;
import javafx.scene.chart.XYChart;
import javafx.scene.chart.XYChart.Data;
import javafx.scene.chart.XYChart.Series;
import javafx.scene.control.Button;
import javafx.scene.control.CheckBox;
@@ -55,9 +52,10 @@ import seng302.visualiser.GameView3D;
import seng302.visualiser.controllers.annotations.ImportantAnnotationController;
import seng302.visualiser.controllers.annotations.ImportantAnnotationDelegate;
import seng302.visualiser.controllers.annotations.ImportantAnnotationsState;
import seng302.visualiser.controllers.dialogs.FinishDialogController;
import seng302.visualiser.fxObjects.ChatHistory;
import seng302.visualiser.fxObjects.assets_2D.BoatObject;
import seng302.visualiser.fxObjects.assets_2D.WindArrow;
import seng302.visualiser.fxObjects.assets_3D.BoatObject;
/**
* Controller class that manages the display of a race
@@ -85,7 +83,8 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel
@FXML
private Label timerLabel;
@FXML
private StackPane contentAnchorPane;
private StackPane contentStackPane;
private GridPane contentGridPane;
@FXML
private AnchorPane rvAnchorPane;
@@ -124,37 +123,13 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel
private Polyline windArrow = new WindArrow(Color.LIGHTGRAY);
private ObservableList<ClientYacht> selectionComboBoxList = FXCollections.observableArrayList();
private ClientYacht player;
private JFXDialog finishScreenDialog;
private FinishDialogController finishDialogController;
public void initialize() {
Sounds.stopMusic();
Sounds.playRaceMusic();
// Load a default important annotation state
//importantAnnotations = new ImportantAnnotationsState();
//Formatting the y axis of the sparkline
// raceSparkLine.getYAxis().setRotate(180);
// raceSparkLine.getYAxis().setTickLabelRotation(180);
// raceSparkLine.getYAxis().setTranslateX(-5);
//raceSparkLine.visibleProperty().setValue(false);
//raceSparkLine.getYAxis().setAutoRanging(false);
//sparklineYAxis.setTickMarkVisible(false);
//positionVbox.getStylesheets().add(getClass().getResource("/css/master.css").toString());
// raceSparkLine.visibleProperty().setValue(false);
// raceSparkLine.getYAxis().setAutoRanging(false);
// sparklineYAxis.setTickMarkVisible(false);
// positionVbox.getStylesheets().add(getClass().getResource("/css/master.css").toString());
//selectAnnotationBtn.setOnAction(event -> loadSelectAnnotationView());
// rvAnchorPane.prefWidthProperty().bind(ViewManager.getInstance().getDecorator().widthProperty());
// rvAnchorPane.prefHeightProperty().bind(ViewManager.getInstance().getDecorator().heightProperty());
// selectAnnotationBtn.setOnAction(event -> loadSelectAnnotationView());
// windArrowHolder.getChildren().addAll(windArrow);
// windArrow.setLayoutX(windArrowHolder.getWidth() / 2);
// windArrow.setLayoutY(windArrowHolder.getHeight() / 2);
// selectAnnotationBtn.setOnAction(event -> loadSelectAnnotationView());
chatInput.lengthProperty().addListener((obs, oldLen, newLen) -> {
if (newLen.intValue() > CHAT_LIMIT) {
chatInput.setText(chatInput.getText().substring(0, CHAT_LIMIT));
@@ -168,25 +143,42 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel
chatHistory.prefHeightProperty().bind(
chatHistoryHolder.heightProperty()
);
// chatHistory.setFitToWidth(true);
// chatHistory.setFitToHeight(true);
// chatHistory.textProperty().addListener((obs, oldValue, newValue) -> {
// chatHistory.setScrollTop(Double.MAX_VALUE);
// });
rvAnchorPane.setOnMouseClicked((event) ->
rvAnchorPane.requestFocus()
);
contentStackPane.setOnMouseClicked(event -> {
contentStackPane.requestFocus();
});
Platform.runLater(contentStackPane::requestFocus);
//Makes the chat history non transparent when clicked on
chatInput.focusedProperty().addListener(new ChangeListener<Boolean>() {
@Override
public void changed(ObservableValue<? extends Boolean> observable, Boolean oldValue,
Boolean newValue) {
if (newValue) {
chatHistory.increaseOpacity();
} else {
chatHistory.decreaseOpacity();
}
chatInput.focusedProperty().addListener((observable, oldValue, newValue) -> {
if (newValue) {
chatHistory.increaseOpacity();
} else {
chatHistory.decreaseOpacity();
}
});
}
public void showFinishDialog(ArrayList<ClientYacht> finishedBoats) {
raceState.setRaceStarted(false);
createFinishDialog(finishedBoats);
}
/**
* Create finishScreenDialog and set up finishDialogController.
*/
private void createFinishDialog(ArrayList<ClientYacht> finishedBoats) {
FXMLLoader dialog = new FXMLLoader(
getClass().getResource("/views/dialogs/RaceFinishDialog.fxml"));
Platform.runLater(() -> {
try {
finishScreenDialog = new JFXDialog(contentStackPane, dialog.load(),
JFXDialog.DialogTransition.CENTER);
finishDialogController = dialog.getController();
finishDialogController.setFinishedBoats(finishedBoats);
finishScreenDialog.show();
} catch (IOException e) {
e.printStackTrace();
}
});
}
@@ -205,23 +197,18 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel
while (c.next()) {
if (c.wasPermutated()) {
updateOrder(raceState.getPlayerPositions());
updateSparkLine();
}
}
});
updateOrder(raceState.getPlayerPositions());
gameView = new GameView3D();
// gameView.setFrameRateFXText(fpsDisplay);
Platform.runLater(() -> {
contentAnchorPane.getChildren().add(0, gameView.getAssets());
contentStackPane.getChildren().add(0, gameView.getAssets());
((SubScene) gameView.getAssets()).widthProperty()
.bind(ViewManager.getInstance().getStage().widthProperty());
((SubScene) gameView.getAssets()).heightProperty()
.bind(ViewManager.getInstance().getStage().heightProperty());
System.out.println(((SubScene) gameView.getAssets()).getHeight());
System.out.println(((SubScene) gameView.getAssets()).getWidth());
});
gameView.setBoats(new ArrayList<>(participants.values()));
gameView.updateBorder(raceData.getCourseLimit());
@@ -229,11 +216,10 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel
gameView.updateCourse(
new ArrayList<>(raceData.getCompoundMarks().values()), raceData.getMarkSequence()
);
// gameView.enableZoom();
gameView.setBoatAsPlayer(player);
// gameView.startRace();
// raceState.addCollisionListener(gameView::drawCollision);
raceState.windDirectionProperty().addListener((obs, oldDirection, newDirection) -> {
gameView.setWindDir(newDirection.doubleValue());
Platform.runLater(() -> updateWindDirection(newDirection.doubleValue()));
@@ -245,34 +231,8 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel
updateWindDirection(raceState.windDirectionProperty().doubleValue());
updateWindSpeed(raceState.getWindSpeed());
});
// gameView.setWindDir(raceState.windDirectionProperty().doubleValue());
//TODO extract chat stuff
// raceState.addCollisionListener(gameView::drawCollision);
// raceState.windDirectionProperty().addListener((obs, oldDirection, newDirection) -> {
// gameView.setWindDir(newDirection.doubleValue());
// updateWindDirection(newDirection.doubleValue());
// });
// raceState.windSpeedProperty().addListener((obs, oldSpeed, newSpeed) -> {
// updateWindSpeed(newSpeed.doubleValue());
// });
// updateWindDirection(raceState.windDirectionProperty().doubleValue());
// updateWindSpeed(raceState.getWindSpeed());
// gameView.setWindDir(raceState.windDirectionProperty().doubleValue());
// chatInput.focusedProperty().addListener((obs, oldValue, newValue) -> {
// if (newValue) {
// gameView.disableZoom();
// } else {
// gameView.enableZoom();
// }
// });
Platform.runLater(() -> {
initializeUpdateTimer();
// initialiseFPSCheckBox();
// initialiseAnnotationSlider();
// initialiseBoatSelectionComboBox();
// initialiseSparkLine();
});
gameView.setWindDir(raceState.windDirectionProperty().doubleValue());
Platform.runLater(this::initializeUpdateTimer);
}
/**
@@ -313,137 +273,6 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel
}
}
private void initialiseFPSCheckBox() {
// toggleFps.selectedProperty().addListener((obs, oldVal, newVal) ->
// gameView.setFPSVisibility(toggleFps.isSelected())
// );
}
private void initialiseAnnotationSlider() {
// annotationSlider.setLabelFormatter(new StringConverter<Double>() {
// @Override
// public String toString(Double n) {
// if (n == 0) {
// return "None";
// }
// if (n == 1) {
// return "Important";
// }
// if (n == 2) {
// return "All";
// }
// return "All";
// }
//
// @Override
// public Double fromString(String s) {
// switch (s) {
// case "None":
// return 0d;
// case "Important":
// return 1d;
// case "All":
// return 2d;
//
// default:
// return 2d;
// }
// }
// });
// annotationSlider.setValue(2);
// annotationSlider.valueProperty().addListener((obs, oldVal, newVal) ->
// setAnnotations((int) annotationSlider.getValue())
// );
}
/**
* Used to add any new yachts into the race that may have started late or not have had data received yet
*/
private void updateSparkLine(){
// // TODO: 2/08/17 there is about 0 chance of this working. Once we are keeping track of boat positions it can be fixed.
// // Collect the racing yachts that aren't already in the chart
// sparkLineData.clear();
// List<ClientYacht> sparkLineCandidates = new ArrayList<>(participants.values());
// // Create a new data series for new yachts
// sparkLineCandidates
// .stream()
// .filter(yacht -> yacht.getPosition() != null)
// .forEach(yacht -> {
// Series<String, Double> yachtData = new Series<>();
// yachtData.setName(yacht.getSourceId().toString());
// yachtData.getData().add(
// new Data<>(
// Integer.toString(yacht.getLegNumber()),
// 1.0 + participants.size() - yacht.getPosition()
// )
// );
// sparkLineData.add(yachtData);
// });
//
// // Lambda function to sort the series in order of leg (later legs shown more to the right)
// sparkLineData.sort((o1, o2) -> {
// Integer leg1 = Integer.parseInt(o1.getData().get(o1.getData().size()-1).getXValue());
// Integer leg2 = Integer.parseInt(o2.getData().get(o2.getData().size()-1).getXValue());
// if (leg2 < leg1){
// return 1;
// } else {
// return -1;
// }
// });
//
// // Adds the new data series to the sparkline (and set the colour of the series)
// Platform.runLater(() -> {
// sparkLineData
// .stream()
// .filter(spark -> !raceSparkLine.getData().contains(spark))
// .forEach(spark -> {
// raceSparkLine.getData().add(spark);
// spark.getNode().lookup(".chart-series-line").setStyle("-fx-stroke:" + getBoatColorAsRGB(spark.getName()));
// });
// });
}
private void initialiseSparkLine() {
// sparklineYAxis.setUpperBound(participants.size() + 1);
// raceSparkLine.setCreateSymbols(false);
}
/**
* Updates the yachts sparkline of the desired yacht and using the new leg number
* @param yacht The yacht to be updated on the sparkline
* @param legNumber the leg number that the position will be assigned to
*/
void updateYachtPositionSparkline(ClientYacht yacht, Integer legNumber){
for (XYChart.Series<String, Double> positionData : sparkLineData) {
positionData.getData().add(
new Data<>(
Integer.toString(legNumber),
1.0 + participants.size() - yacht.getPlacing()
)
);
}
}
/**
* gets the rgb string of the yachts colour to use for the chart via css
* @param yachtId id of yacht passed in to get the yachts colour
* @return the colour as an rgb string
*/
private String getBoatColorAsRGB(String yachtId){
Color color = participants.get(Integer.valueOf(yachtId)).getColour();
if (color == null){
return String.format("#%02X%02X%02X",255,255,255);
}
return String.format( "#%02X%02X%02X",
(int)( color.getRed() * 255 ),
(int)( color.getGreen() * 255 ),
(int)( color.getBlue() * 255 )
);
}
/**
* Initialises a timer which updates elements of the RaceView such as wind direction, yacht
* orderings etc.. which are dependent on the info from the stream parser constantly.
@@ -801,7 +630,7 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel
public String readChatInput() {
String chat = chatInput.getText();
chatInput.clear();
rvAnchorPane.requestFocus();
contentStackPane.requestFocus();
return chat;
}
@@ -112,17 +112,25 @@ public class ServerListController implements Initializable, ServerListenerDelega
serverListVBox.getChildren().add(noServersFound);
// Set up dialog for server creation
serverListHostButton.setOnAction(action -> {
showServerCreationDialog();
});
}
/**
* Shows Server Creation Dialog when "Host" button is clicked.
*/
private void showServerCreationDialog() {
Platform.runLater(() -> {
FXMLLoader dialogContent = new FXMLLoader(getClass().getResource(
"/views/dialogs/ServerCreationDialog.fxml"));
try {
JFXDialog dialog = new JFXDialog(serverListMainStackPane, dialogContent.load(),
DialogTransition.CENTER);
serverListHostButton.setOnAction(action -> {
dialog.show();
Sounds.playButtonClick();
});
dialog.show();
Sounds.playButtonClick();
} catch (IOException e) {
e.printStackTrace();
logger.warn("Could not create Server Creation Dialog.");
}
});
@@ -2,6 +2,9 @@ package seng302.visualiser.controllers;
import com.jfoenix.controls.JFXButton;
import com.jfoenix.controls.JFXDecorator;
import com.jfoenix.controls.JFXDialog;
import com.jfoenix.controls.JFXDialog.DialogTransition;
import com.jfoenix.controls.JFXSnackbar;
import com.jfoenix.svg.SVGGlyph;
import java.io.IOException;
import java.util.HashMap;
@@ -15,6 +18,7 @@ import javafx.scene.Scene;
import javafx.scene.SceneAntialiasing;
import javafx.scene.image.Image;
import javafx.scene.layout.HBox;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
import org.slf4j.Logger;
@@ -23,6 +27,8 @@ import seng302.gameServer.ServerAdvertiser;
import seng302.utilities.BonjourInstallChecker;
import seng302.utilities.Sounds;
import seng302.visualiser.GameClient;
import seng302.visualiser.controllers.dialogs.KeyBindingDialogController;
import seng302.visualiser.controllers.dialogs.PopupDialogController;
public class ViewManager {
@@ -32,12 +38,9 @@ public class ViewManager {
private HashMap<String, String> properties; //TODO is this the best way to do this??
private ObservableList<String> playerList;
private Logger logger = LoggerFactory.getLogger(ViewManager.class);
public Stage getStage() {
return stage;
}
private Stage stage;
private JFXSnackbar jfxSnackbar;
private JFXDialog keyBindingDialog;
private ViewManager() {
properties = new HashMap<>();
@@ -70,10 +73,8 @@ public class ViewManager {
decorator.applyCss();
decorator.getStylesheets()
.add(getClass().getResource("/css/Master.css").toExternalForm());
setDecorator(decorator);
gameClient = new GameClient(decorator);
setDecorator(decorator);
stage.getIcons().add(new Image(getClass().getResourceAsStream("/PP.png")));
Scene scene = new Scene(decorator, 1200, 800, false, SceneAntialiasing.BALANCED);
@@ -101,6 +102,8 @@ public class ViewManager {
gameClient.stopGame();
System.exit(0);
});
jfxSnackbar = new JFXSnackbar(decorator);
}
/**
@@ -112,28 +115,56 @@ public class ViewManager {
private void setDecorator(JFXDecorator newDecorator) {
decorator = newDecorator;
decorator.setOnCloseButtonAction(() -> {
gameClient.stopGame();
System.exit(0);
});
//Injecting a volume toggle into the decorator.
//Get the button box
HBox btns = (HBox) decorator.getChildren().get(0);
//Create settings button -- [WIP]
JFXButton btnKeyBinding = new JFXButton();
btnKeyBinding.setText(" Key Bindings");
btnKeyBinding.setStyle("-fx-text-fill:#fff");
btnKeyBinding.getStyleClass().add("jfx-decorator-button");
btnKeyBinding.setCursor(Cursor.HAND);
btnKeyBinding.setFocusTraversable(false);
btnKeyBinding.setOnMouseClicked(event -> Platform.runLater(() -> {
try {
if (!checkDialogOpened(decorator.getChildren())) {
showKeyBindingDialog();
}
} catch (IOException e) {
logger.warn("Something went wrong when opening key bind dialog");
}
}));
//Create new button
JFXButton btnMute = new JFXButton();
btnMute.setText(" Toggle Sound");
btnMute.setStyle("-fx-text-fill:#fff");
btnMute.getStyleClass().add("jfx-decorator-button");
btnMute.setCursor(Cursor.HAND);
btnMute.setFocusTraversable(false);
//Create Graphics
SVGGlyph spacer = new SVGGlyph(0, "SPACER", "", Color.WHITE);
SVGGlyph volumeOn = new SVGGlyph(0, "VOLUME_ON",
"M39.389,13.769 22.235,28.606 6,28.606 6,47.699 21.989,47.699 39.389,62.75 39.389,13.769 M 48.128,49.03 C 50.057,45.934 51.19,42.291 51.19,38.377 C 51.19,34.399 50.026,30.703 48.043,27.577 M 55.082,20.537 C 58.777,25.523 60.966,31.694 60.966,38.377 C 60.966,44.998 58.815,51.115 55.178,56.076 M 61.71,62.611 C 66.977,55.945 70.128,47.531 70.128,38.378 C 70.128,29.161 66.936,20.696 61.609,14.01",
"M0,6 L0,12 L4,12 L9,17 L9,1 L4,6 L0,6 L0,6 Z M13.5,9 C13.5,7.2 12.5,5.7 11,5 L11,13 C12.5,12.3 13.5,10.8 13.5,9 L13.5,9 Z M11,0.2 L11,2.3 C13.9,3.2 16,5.8 16,9 C16,12.2 13.9,14.8 11,15.7 L11,17.8 C15,16.9 18,13.3 18,9 C18,4.7 15,1.1 11,0.2 L11,0.2 Z",
Color.WHITE);
SVGGlyph volumeOff = new SVGGlyph(0, "VOLUME_ON",
"M39.389,13.769 22.235,28.606 6,28.606 6,47.699 21.989,47.699 39.389,62.75 39.389,13.769",
"M13.5,9 C13.5,7.2 12.5,5.7 11,5 L11,7.2 L13.5,9.7 L13.5,9 L13.5,9 Z M16,9 C16,9.9 15.8,10.8 15.5,11.6 L17,13.1 C17.7,11.9 18,10.4 18,8.9 C18,4.6 15,1 11,0.1 L11,2.2 C13.9,3.2 16,5.8 16,9 L16,9 Z M1.3,0 L0,1.3 L4.7,6 L0,6 L0,12 L4,12 L9,17 L9,10.3 L13.3,14.6 C12.6,15.1 11.9,15.5 11,15.8 L11,17.9 C12.4,17.6 13.6,17 14.7,16.1 L16.7,18.1 L18,16.8 L9,7.8 L1.3,0 L1.3,0 Z M9,1 L6.9,3.1 L9,5.2 L9,1 L9,1 Z",
Color.WHITE);
SVGGlyph keyBindingGlyph = new SVGGlyph(0, "KEY_BINDING",
"M20 5H4c-1.1 0-1.99.9-1.99 2L2 17c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V7c0-1.1-.9-2-2-2zm-9 3h2v2h-2V8zm0 3h2v2h-2v-2zM8 8h2v2H8V8zm0 3h2v2H8v-2zm-1 2H5v-2h2v2zm0-3H5V8h2v2zm9 7H8v-2h8v2zm0-4h-2v-2h2v2zm0-3h-2V8h2v2zm3 3h-2v-2h2v2zm0-3h-2V8h2v2z",
Color.WHITE);
volumeOn.setSize(16, 16);
volumeOff.setSize(12, 16);
volumeOff.setSize(16, 16);
spacer.setSize(40, 16);
keyBindingGlyph.setSize(24, 16);
// Determine which graphic should go on the button
if (Sounds.isMusicMuted() && Sounds.isSoundEffectsMuted()) {
@@ -142,9 +173,12 @@ public class ViewManager {
btnMute.setGraphic(volumeOn);
}
btnKeyBinding.setGraphic(keyBindingGlyph);
// Add Buttons
btns.getChildren().add(0, spacer);
btns.getChildren().add(0, btnMute);
btns.getChildren().add(0, btnKeyBinding);
btnMute.setOnAction((action) -> {
Sounds.toggleAllSounds();
if (btnMute.getGraphic().equals(volumeOff)) {
@@ -156,6 +190,84 @@ public class ViewManager {
}
/**
* Recursively find JFXDialog given a starting node. Will traverse children of StackPane.
*
* @param nodes children nodes to be check.
* @return true if node contains JFXDialog.
*/
private Boolean checkDialogOpened(ObservableList<Node> nodes) {
boolean foundJFXDialog = false;
for (Node node : nodes) {
if (node instanceof JFXDialog) {
return true;
} else if (node instanceof StackPane) {
foundJFXDialog = checkDialogOpened(((StackPane) node).getChildren());
}
}
return foundJFXDialog;
}
private void showKeyBindingDialog() throws IOException {
FXMLLoader dialogContent = new FXMLLoader(getClass().getResource(
"/views/dialogs/KeyBindingDialog.fxml"));
for (Node node : decorator.getChildren()) {
if (node instanceof StackPane) {
keyBindingDialog = new JFXDialog((StackPane) node,
dialogContent.load(),
DialogTransition.CENTER);
KeyBindingDialogController keyBindingDialogController = dialogContent
.getController();
keyBindingDialogController.setGameClient(this.gameClient);
keyBindingDialog.show();
decorator.requestFocus();
Sounds.playButtonClick();
}
}
}
public void closeKeyBindingDialog() {
keyBindingDialog.close();
}
public PopupDialogController showPopupDialog() {
FXMLLoader dialogContent = new FXMLLoader(
getClass().getResource("/views/dialogs/PopupDialog.fxml"));
for (Node node : decorator.getChildren()) {
if (node instanceof StackPane) {
try {
JFXDialog dialog = new JFXDialog((StackPane) node, dialogContent.load(),
DialogTransition.CENTER);
PopupDialogController popupDialogController = dialogContent.getController();
popupDialogController.setPopupDialog(dialog);
dialog.show();
return popupDialogController;
} catch (IOException e) {
logger.error("Cannot load Popup dialog");
}
}
}
return null;
}
/**
* Show a snackbar at the bottom of the app for 1 second.
*
* @param snackbarText text to be displayed.
*/
public void showSnackbar(String snackbarText, boolean isWarning) {
if (isWarning) {
decorator.getStylesheets()
.add(getClass().getResource("/css/dialogs/Snackbar.css").toExternalForm());
} else {
if (decorator.getStylesheets().size() > 1) {
decorator.getStylesheets().remove(1);
}
}
jfxSnackbar.show(snackbarText, 1500);
}
/**
* Determines if a PC has compatibility with the bonjour protocol for server detection.
*/
@@ -218,6 +330,7 @@ public class ViewManager {
/**
* Change the view to the Lobby Screen
*
* @param disableReadyButton Boolean value so that clients can't try start a game.
* @return A LobbyController object for the Lobby Screen.
*/
@@ -240,19 +353,18 @@ public class ViewManager {
/**
* Sets up the view for the race. Creating a new decorator and destroying the old one.
*
* @return A RaceViewController for the race view screen.
*/
public RaceViewController loadRaceView() {
FXMLLoader loader = loadFxml("/views/RaceView.fxml");
// have to create a new stage and set the race view maximized as JFoenix decorator has
// bug causes stage cannot be fully maximised.
Platform.runLater(() -> {
try {
stage.close();
stage = new Stage();
JFXDecorator decorator = new JFXDecorator(stage, loader.load(), false, true, true);
decorator.setCustomMaximize(true);
decorator.applyCss();
@@ -260,21 +372,16 @@ public class ViewManager {
.add(getClass().getResource("/css/Master.css").toExternalForm());
setDecorator(decorator);
Scene scene = new Scene(decorator);
RaceViewController raceViewController = loader.getController();
gameClient.setRaceViewController(raceViewController);
// set key press event to catch key stoke
scene.setOnKeyPressed(gameClient::keyPressed);
scene.setOnKeyReleased(gameClient::keyReleased);
// uncomment to make it full screen
// Rectangle2D visualBounds = Screen.getPrimary().getVisualBounds();
// stage.setX(visualBounds.getMinX());
// stage.setY(visualBounds.getMinY());
// stage.setWidth(visualBounds.getWidth());
// stage.setHeight(visualBounds.getHeight());
// stage.setMaximized(true);
// stage.setFullScreen(true);
stage.setMinHeight(500);
stage.setMinWidth(800);
stage.setTitle("Party Parrots At Sea");
stage.getIcons().add(new Image(getClass().getResourceAsStream("/PP.png")));
stage.setOnCloseRequest(e -> closeAll());
stage.setScene(scene);
stage.show();
@@ -283,7 +390,7 @@ public class ViewManager {
}
});
while (loader.getController() == null){
while (loader.getController() == null) {
try {
Thread.sleep(50);
} catch (InterruptedException e) {
@@ -293,4 +400,9 @@ public class ViewManager {
return loader.getController();
}
public Stage getStage() {
return stage;
}
}
@@ -6,6 +6,7 @@ import javafx.scene.control.Label;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import seng302.model.ClientYacht;
import seng302.visualiser.fxObjects.assets_3D.BoatMeshType;
import seng302.visualiser.fxObjects.assets_3D.BoatModel;
import seng302.visualiser.fxObjects.assets_3D.ModelFactory;
@@ -24,11 +25,13 @@ public class PlayerCell {
private String name;
private Color boatColor;
private Integer playerId;
private BoatMeshType boatType;
public PlayerCell(Integer playerId, String playerName, Color color) {
public PlayerCell(Integer playerId, ClientYacht yacht) {
this.playerId = playerId;
this.name = playerName;
this.boatColor = color;
this.name = yacht.getBoatName();
this.boatColor = yacht.getColour();
this.boatType = yacht.getBoatType();
}
public void initialize() {
@@ -37,7 +40,7 @@ public class PlayerCell {
// Add Rotating Boat to Player Cell with players color on it.
Group group = new Group();
boatPane.getChildren().add(group);
BoatModel bo = ModelFactory.boatIconView(BoatMeshType.DINGHY, this.boatColor);
BoatModel bo = ModelFactory.boatIconView(boatType, boatColor);
group.getChildren().add(bo.getAssets());
}
@@ -69,7 +69,6 @@ public class ServerCell implements Initializable {
* Attempts to connect to the chosen server using the button on the serverCell.
*/
private void joinServer() {
System.out.println("Connecting to " + serverName.getText());
ViewManager.getInstance().getGameClient().runAsClient(hostName, portNumber);
}
@@ -6,15 +6,25 @@ import com.jfoenix.controls.JFXTextField;
import com.jfoenix.validation.RequiredFieldValidator;
import java.net.URL;
import java.util.ResourceBundle;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.geometry.Insets;
import javafx.scene.Group;
import javafx.scene.PointLight;
import javafx.scene.control.ProgressBar;
import javafx.scene.layout.Background;
import javafx.scene.layout.BackgroundFill;
import javafx.scene.layout.CornerRadii;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import seng302.gameServer.messages.CustomizeRequestType;
import seng302.utilities.Sounds;
import seng302.visualiser.ClientToServerThread;
import seng302.visualiser.controllers.LobbyController;
import seng302.visualiser.controllers.ViewManager;
import seng302.visualiser.fxObjects.assets_3D.BoatMeshType;
import seng302.visualiser.fxObjects.assets_3D.BoatModel;
import seng302.visualiser.fxObjects.assets_3D.ModelFactory;
import seng302.visualiser.validators.FieldLengthValidator;
import seng302.visualiser.validators.ValidationTools;
@@ -24,27 +34,35 @@ public class BoatCustomizeController implements Initializable{
@FXML
private JFXColorPicker colorPicker;
@FXML
private ProgressBar speedBar;
@FXML
private ProgressBar accelBar;
@FXML
private ProgressBar handleBar;
@FXML
private JFXButton submitBtn;
@FXML
private JFXTextField boatName;
@FXML
void colorChanged(ActionEvent event) {
Color color = colorPicker.getValue();
private Pane boatPane;
@FXML
void colorChanged() {
refreshBoat();
}
//---------FXML END---------//
private ClientToServerThread socketThread;
private LobbyController lobbyController;
private BoatMeshType currentBoat;
private Double maxSpeedMultiplier = 1.0;
private Double maxTurnRateMultiplier = 1.0;
private Double maxAccelerationMultiplier = 1.0;
@Override
public void initialize(URL location, ResourceBundle resources) {
submitBtn.setOnMouseReleased(event -> {
Sounds.playButtonClick();
submitCustomization();
});
socketThread = ViewManager.getInstance().getGameClient().getServerThread();
findMaxStats();
RequiredFieldValidator playerNameReqValidator = new RequiredFieldValidator();
playerNameReqValidator.setMessage("Player name required.");
@@ -52,6 +70,13 @@ public class BoatCustomizeController implements Initializable{
playerNameLengthValidator.setMessage("Player name too long.");
boatName.setValidators(playerNameLengthValidator, playerNameReqValidator);
boatPane.setBackground(
new Background(new BackgroundFill(Color.SKYBLUE, CornerRadii.EMPTY, Insets.EMPTY)));
submitBtn.setOnMouseReleased(event -> {
Sounds.playButtonClick();
submitCustomization();
});
submitBtn.setOnMouseEntered(e -> Sounds.playHoverSound());
}
@@ -77,7 +102,10 @@ public class BoatCustomizeController implements Initializable{
colorArray[2] = (byte) blue;
socketThread.sendCustomizationRequest(CustomizeRequestType.COLOR, colorArray);
socketThread.sendCustomizationRequest(CustomizeRequestType.SHAPE, currentBoat.toString().getBytes());
lobbyController.closeCustomizationDialog();
}
}
@@ -92,4 +120,61 @@ public class BoatCustomizeController implements Initializable{
public void setParentController(LobbyController lobbyController){
this.lobbyController = lobbyController;
}
public void setCurrentBoat(String boatType) {
currentBoat = BoatMeshType.valueOf(boatType);
displayCurrentBoat();
refreshStatBars(currentBoat);
}
public void nextBoat() {
currentBoat = BoatMeshType.getNextBoatType(currentBoat);
displayCurrentBoat();
refreshStatBars(currentBoat);
}
public void prevBoat() {
currentBoat = BoatMeshType.getPrevBoatType(currentBoat);
displayCurrentBoat();
refreshStatBars(currentBoat);
}
private void displayCurrentBoat() {
boatPane.getChildren().clear();
Group group = new Group();
boatPane.getChildren().add(group);
BoatModel bo = ModelFactory.boatCustomiseView(currentBoat, colorPicker.getValue());
group.getChildren().add(bo.getAssets());
group.getChildren().add(new PointLight());
}
private void refreshBoat() {
boatPane.getChildren().clear();
Group group = new Group();
boatPane.getChildren().add(group);
BoatModel bo = ModelFactory.boatCustomiseView(currentBoat, colorPicker.getValue());
group.getChildren().add(bo.getAssets());
refreshStatBars(currentBoat);
}
private void findMaxStats() {
for (BoatMeshType bmt: BoatMeshType.values()) {
if (bmt.turnStep > maxTurnRateMultiplier) {
maxTurnRateMultiplier = bmt.turnStep;
}
if (bmt.maxSpeedMultiplier > maxSpeedMultiplier) {
maxSpeedMultiplier = bmt.maxSpeedMultiplier;
}
if (bmt.accelerationMultiplier > maxAccelerationMultiplier) {
maxAccelerationMultiplier = bmt.accelerationMultiplier;
}
}
}
private void refreshStatBars(BoatMeshType bo) {
speedBar.setProgress((bo.maxSpeedMultiplier) / maxSpeedMultiplier);
accelBar.setProgress(bo.accelerationMultiplier / maxAccelerationMultiplier);
handleBar.setProgress(bo.turnStep / maxTurnRateMultiplier);
}
}
@@ -0,0 +1,38 @@
package seng302.visualiser.controllers.dialogs;
import com.jfoenix.controls.JFXButton;
import com.jfoenix.controls.JFXListView;
import java.net.URL;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.ResourceBundle;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.Label;
import seng302.model.ClientYacht;
import seng302.visualiser.controllers.ViewManager;
public class FinishDialogController implements Initializable {
//--------FXML BEGIN--------//
@FXML
private Label raceFinishLabel;
@FXML
private JFXListView<Label> finishersList;
@FXML
private JFXButton playAgain;
//---------FXML END---------//
@Override
public void initialize(URL location, ResourceBundle resources) {
playAgain.setOnAction(event -> ViewManager.getInstance().goToStartView());
}
public void setFinishedBoats(ArrayList<ClientYacht> finishedBoats) {
finishersList.getItems().clear();
for (int i = 0; i < finishedBoats.size(); i++) {
finishersList.getItems().add(new Label(Integer.toString(i+1) +". " + finishedBoats.get(i).getBoatName()));
}
}
}
@@ -0,0 +1,194 @@
package seng302.visualiser.controllers.dialogs;
import com.jfoenix.controls.JFXButton;
import com.jfoenix.controls.JFXToggleButton;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.ResourceBundle;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.input.KeyEvent;
import seng302.model.GameKeyBind;
import seng302.model.KeyAction;
import seng302.visualiser.GameClient;
import seng302.visualiser.controllers.ViewManager;
public class KeyBindingDialogController implements Initializable {
//--------FXML BEGIN--------//
@FXML
private Label keyBindingDialogHeader;
@FXML
private Label closeLabel;
@FXML
private JFXButton zoomInbtn;
@FXML
private JFXButton zoomOutBtn;
@FXML
private JFXButton vmgBtn;
@FXML
private JFXButton sailInOutBtn;
@FXML
private JFXButton tackGybeBtn;
@FXML
private JFXButton upwindBtn;
@FXML
private JFXButton downwindBtn;
@FXML
private JFXButton resetBtn;
@FXML
private Label upwindLabel;
@FXML
private Label downwindLabel;
@FXML
private JFXToggleButton turningToggle;
@FXML
private JFXButton viewButton;
@FXML
private JFXButton rightButton;
@FXML
private JFXButton leftButton;
@FXML
private JFXButton forwardButton;
@FXML
private JFXButton backwardButton;
//---------FXML END---------//
private GameKeyBind gameKeyBind;
private List<JFXButton> buttons = new ArrayList<>();
private Map<Button, KeyAction> buttonActionMap;
private GameClient gameClient; // to send turning mode packet
@Override
public void initialize(URL location, ResourceBundle resources) {
gameKeyBind = GameKeyBind.getInstance();
buttons = new ArrayList<>();
Collections.addAll(buttons,
zoomInbtn, zoomOutBtn, vmgBtn, sailInOutBtn, tackGybeBtn, upwindBtn, downwindBtn,
viewButton, rightButton, leftButton, forwardButton, backwardButton);
bindButtonWithAction();
loadKeyBind();
buttons.forEach(button -> {
button.setOnMouseEntered(event -> mouseEnter(button));
button.setOnMousePressed(event -> buttonPressed(button));
button.setOnMouseExited(event -> mouseExit(button));
button.setOnKeyPressed(event -> keyPressed(event, button));
});
turningToggle.setOnMouseClicked(event -> toggleTurningMode());
resetBtn.setOnMouseClicked(event -> {
gameKeyBind.setToDefault();
loadKeyBind();
});
closeLabel.setOnMouseClicked(event -> ViewManager.getInstance().closeKeyBindingDialog());
}
/**
* Set buttons' label according to GameKeyBind settings
*/
private void loadKeyBind() {
buttons.forEach(
button -> button
.setText(gameKeyBind.getKeyCode(buttonActionMap.get(button)).getName()));
turningToggle.setSelected(gameKeyBind.isContinuouslyTurning());
if (gameKeyBind.isContinuouslyTurning()) {
upwindLabel.setText("ClOCKWISE TURNING");
downwindLabel.setText("ANTICLOCKWISE TURNING");
} else {
upwindLabel.setText("UPWIND");
downwindLabel.setText("DOWNWIND");
}
}
/**
* Bind buttons with specific action in a map.
*/
private void bindButtonWithAction() {
buttonActionMap = new HashMap<>();
for (int i = 0; i < 12; i++) {
buttonActionMap.put(buttons.get(i), KeyAction.getType(i + 1));
}
}
/**
* Prompt success / failure message for reassigning key action
*/
private void showSnackBar(String message, Boolean isWarning) {
ViewManager.getInstance().showSnackbar(message, isWarning);
}
/**
* When a mouse enters the button, the color and font size should change to highlight
* @param button
*/
private void mouseEnter(Button button) {
button.setStyle(""
+ "-fx-background-color: -fx-pp-theme-color;"
+ "-fx-text-fill: -fx-pp-front-color;"
+ "-fx-font-size: 15;");
}
/**
* Prompt "press key..." to inform users assign a new key bind by pressing a key
* @param button
*/
private void buttonPressed(Button button) {
button.setText("PRESS KEY...");
}
/**
* When mouse leaves the button, return the button to the normal state in terms of text,
* color and font size
* @param button
*/
private void mouseExit(Button button) {
button.setText(GameKeyBind.getInstance().getKeyCode(buttonActionMap.get(button)).getName());
button.setStyle(""
+ "-fx-background-color: -fx-pp-front-color; "
+ "-fx-text-fill: -fx-pp-theme-color; "
+ "-fx-font-size: 13;");
keyBindingDialogHeader.requestFocus();
}
/**
* When a key is pressed, check if the new binding conflicts to any existed settings, if not
* assign the selected action with the new key binding to GameKeyBind.
* @param event
* @param button
*/
private void keyPressed(KeyEvent event, Button button) {
event.consume();
KeyAction buttonAction = buttonActionMap.get(button);
if (gameKeyBind.bindKeyToAction(event.getCode(), buttonAction)) {
showSnackBar(button.getId() + " is set to " + event.getCode().getName(), false);
button.setText(gameKeyBind.getKeyCode(buttonAction).getName());
} else {
loadKeyBind();
showSnackBar(event.getCode().getName() + " is already in use", true);
}
}
/**
* When the turning mode is toggled, update gameKeyBind and send out packet to notify the server
*/
private void toggleTurningMode() {
gameKeyBind.toggleTurningMode();
gameClient.sendToggleTurningModePacket();
loadKeyBind();
}
public void setGameClient(GameClient gameClient) {
this.gameClient = gameClient;
}
}
@@ -0,0 +1,56 @@
package seng302.visualiser.controllers.dialogs;
import com.jfoenix.controls.JFXButton;
import com.jfoenix.controls.JFXDialog;
import java.net.URL;
import java.util.ResourceBundle;
import javafx.event.EventHandler;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.Label;
import javafx.scene.input.MouseEvent;
public class PopupDialogController implements Initializable {
@FXML
private Label headerLabel;
@FXML
private Label contentLabel;
@FXML
private Label closeLabel;
@FXML
private JFXButton optionButton;
@FXML
private JFXDialog popupDialog;
@Override
public void initialize(URL location, ResourceBundle resources) {
}
public void setContent(String content) {
this.contentLabel.setText(content);
}
public void setHeader(String header) {
this.headerLabel.setText(header);
}
public void setOptionButton(JFXButton jfxButton) {
this.optionButton = jfxButton;
}
public void setOptionButtonText(String text) {
this.optionButton.setText(text);
}
public void setOptionButtonEventHandler(EventHandler<? super MouseEvent> eventHandler) {
this.optionButton.setOnMouseClicked(eventHandler);
}
public void setPopupDialog(JFXDialog popupDialog) {
this.popupDialog = popupDialog;
this.closeLabel.setOnMouseClicked(event -> this.popupDialog.close());
}
}
@@ -1,18 +1,19 @@
package seng302.visualiser.controllers.dialogs;
import com.jfoenix.controls.JFXButton;
import com.jfoenix.controls.JFXCheckBox;
import com.jfoenix.controls.JFXSlider;
import com.jfoenix.controls.JFXTextField;
import com.jfoenix.validation.RequiredFieldValidator;
import java.net.URL;
import java.util.ResourceBundle;
import javafx.application.Platform;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.Label;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.AnchorPane;
import seng302.gameServer.ServerDescription;
import seng302.utilities.Sounds;
import seng302.visualiser.MapMaker;
import seng302.visualiser.controllers.ViewManager;
import seng302.visualiser.validators.FieldLengthValidator;
import seng302.visualiser.validators.ValidationTools;
@@ -28,13 +29,40 @@ public class ServerCreationController implements Initializable {
private Label maxPlayersLabel;
@FXML
private JFXButton submitBtn;
@FXML
private JFXButton nextMapButton;
@FXML
private JFXButton lastMapButton;
@FXML
private Label mapNameLabel;
@FXML
private JFXSlider legsSlider;
@FXML
private Label legsSliderLabel;
@FXML
private JFXCheckBox pickupsCheckBox;
@FXML
private AnchorPane mapHolder;
private MapMaker mapMaker = MapMaker.getInstance();
//---------FXML END---------//
public void initialize(URL location, ResourceBundle resources) {
maxPlayersSlider.valueProperty().addListener(
(observable, oldValue, newValue) -> updateMaxPlayerLabel()
);
maxPlayersSlider.setMax(mapMaker.getMaxPlayers());
maxPlayersSlider.setValue(mapMaker.getMaxPlayers());
legsSlider.valueProperty().addListener(
(obs, oldVal, newVal) -> updateLegSliderLabel()
);
legsSlider.setMax(10);
updateMaxPlayerLabel();
maxPlayersSlider.valueProperty().addListener((observable, oldValue, newValue) -> {
updateMaxPlayerLabel();
});
updateLegSliderLabel();
FieldLengthValidator fieldLengthValidator = new FieldLengthValidator(40);
fieldLengthValidator.setMessage("Server name too long.");
@@ -49,6 +77,18 @@ public class ServerCreationController implements Initializable {
validateServerSettings();
});
nextMapButton.setOnMouseReleased(event -> {
Sounds.playButtonClick();
nextMap();
});
lastMapButton.setOnMouseReleased(event -> {
Sounds.playButtonClick();
lastMap();
});
mapHolder.getChildren().setAll(mapMaker.getCurrentGameView());
mapNameLabel.setText(mapMaker.getCurrentRegatta().getCourseName());
}
/**
@@ -69,7 +109,7 @@ public class ServerCreationController implements Initializable {
private void createServer() {
ServerDescription serverDescription = ViewManager.getInstance().getGameClient()
.runAsHost("localhost", 4941, serverName.getText(), (int) maxPlayersSlider
.getValue());
.getValue(), mapMaker.getCurrentRacePath(), (int) legsSlider.getValue(), pickupsCheckBox.isSelected());
ViewManager.getInstance().setProperty("serverName", serverDescription.getName());
ViewManager.getInstance().setProperty("mapName", serverDescription.getMapName());
@@ -80,11 +120,35 @@ public class ServerCreationController implements Initializable {
*/
private void updateMaxPlayerLabel() {
maxPlayersSlider.setValue(Math.floor(maxPlayersSlider.getValue()));
maxPlayersLabel.setText(String.format("YOU SELECTED: %.0f", maxPlayersSlider.getValue()));
maxPlayersLabel.setText(String.format("Max players: %.0f", maxPlayersSlider.getValue()));
}
public void playButtonHoverSound(MouseEvent mouseEvent) {
private void updateLegSliderLabel() {
legsSlider.setValue(Math.floor(legsSlider.getValue()));
legsSliderLabel.setText(
String.format("A section of the race will repeat %.0f times", legsSlider.getValue())
);
}
public void playButtonHoverSound() {
Sounds.playHoverSound();
}
private void nextMap() {
mapMaker.next();
updateMap();
}
private void lastMap() {
mapMaker.previous();
updateMap();
}
private void updateMap() {
mapHolder.getChildren().setAll(mapMaker.getCurrentGameView());
mapNameLabel.setText(mapMaker.getCurrentRegatta().getCourseName());
maxPlayersSlider.setMax(mapMaker.getMaxPlayers());
maxPlayersSlider.setValue(mapMaker.getMaxPlayers());
}
}
@@ -1,10 +1,19 @@
package seng302.visualiser.fxObjects.assets_2D;
package seng302.visualiser.fxObjects;
import javafx.geometry.Point3D;
import javafx.scene.Group;
import javafx.scene.paint.Color;
import javafx.scene.paint.Paint;
import javafx.scene.shape.*;
import javafx.scene.shape.Arc;
import javafx.scene.shape.ArcType;
import javafx.scene.shape.Polygon;
import javafx.scene.shape.Polyline;
import javafx.scene.shape.StrokeLineCap;
import javafx.scene.transform.Rotate;
import javafx.scene.transform.Translate;
import seng302.visualiser.fxObjects.assets_3D.Model;
import seng302.visualiser.fxObjects.assets_3D.ModelFactory;
import seng302.visualiser.fxObjects.assets_3D.ModelType;
// TODO: 16/08/17 this class used to be well written... FeelsBadMan. Maybe lose the ternary operators.
/**
@@ -26,6 +35,47 @@ public class MarkArrowFactory {
public static final double ARROW_HEAD_WIDTH = 6;
public static final double STROKE_WIDTH = 3;
public static Model constructEntryArrow3D (
RoundingSide roundingSide, double angle, ModelType type) {
Model entryArrow = ModelFactory.importModel(type);
double angleDeg = angle;
angle = 180 - angle;
angle = Math.toRadians(angle);
int multiplier = roundingSide == RoundingSide.STARBOARD ? 1 : -1;
double relativeX = multiplier * 5.7 * Math.sin(angle + Math.PI / 2);
double relativeY = multiplier * 5.7 * Math.cos(angle + Math.PI / 2);
double xStart = relativeX + -10 * Math.sin(angle);
double yStart = relativeY + -10 * Math.cos(angle);
entryArrow.getAssets().getTransforms().addAll(
new Translate(xStart, yStart, 0),
new Rotate(angleDeg, new Point3D(0,0,1))
);
return entryArrow;
}
public static Model constructExitArrow3D (
RoundingSide roundingSide, double angle, ModelType type) {
Model exitArrow = ModelFactory.importModel(type);
double angleDeg = angle;
angle = 180 - angle;
angle = Math.toRadians(angle);
int multiplier = roundingSide == RoundingSide.STARBOARD ? 1 : -1;
double xStart = multiplier * 5.7 * Math.sin(angle + Math.PI / 2);
double yStart = multiplier * 5.7 * Math.cos(angle + Math.PI / 2);
exitArrow.getAssets().getTransforms().addAll(
new Translate(xStart, yStart, 0),
new Rotate(angleDeg, new Point3D(0,0,1))
);
return exitArrow;
}
/**
* Creates an entry arrow group showing an arrow into and out of the rounding area. It is centered on (0, 0).
* @param roundingSide The side of the boat that will be closest to the mark.
@@ -35,48 +85,72 @@ public class MarkArrowFactory {
* @return The group containing all JavaFX objects.
*/
public static Group constructEntryArrow (RoundingSide roundingSide, double angleOfEntry,
double angleOfExit, Paint colour) {
if (roundingSide == RoundingSide.PORT && angleOfEntry < angleOfExit && Math.abs(angleOfExit - angleOfEntry) < 180) {
double angleOfExit, Paint colour) {
// Check to see if the the angle around mark would take you inside of it. (less than 180)
// If so make interior angle.
if (roundingSide == RoundingSide.PORT && angleOfEntry < angleOfExit &&
Math.abs(angleOfExit - angleOfEntry) < 180) {
return makeInteriorAngle(roundingSide, angleOfExit, angleOfEntry, colour);
} else if (roundingSide == RoundingSide.STARBOARD && angleOfEntry > angleOfExit && -Math.abs(angleOfEntry - angleOfExit) > -180) {
} else if (roundingSide == RoundingSide.STARBOARD && angleOfEntry > angleOfExit &&
-Math.abs(angleOfEntry - angleOfExit) > -180) {
return makeInteriorAngle(roundingSide, angleOfExit, angleOfEntry, colour);
}
angleOfEntry = 180 - angleOfEntry;
//Create regular exit arrow.
Group arrow = new Group();
Group exitSection = constructExitArrow(roundingSide, angleOfExit, colour);
//Reverse angles to make arc
angleOfEntry = 180 - angleOfEntry;
angleOfExit = 180 - angleOfExit;
//Maker the arc
Arc roundSection = new Arc(
0, 0, MARK_ARROW_SEPARATION, MARK_ARROW_SEPARATION,
(roundingSide == RoundingSide.PORT ? -180 : 0) + angleOfEntry,
//Where to start drawing arc from
(roundingSide == RoundingSide.PORT ? 0 : angleOfEntry),
//Which way to go around the mark. (clockwise vs anticlockwise)
roundingSide == RoundingSide.PORT ? Math.abs(angleOfExit - angleOfEntry) : -Math.abs(angleOfEntry - angleOfExit)
);
roundSection.setStrokeWidth(STROKE_WIDTH);
roundSection.setType(ArcType.OPEN);
roundSection.setStroke(colour);
roundSection.setFill(new Color(0,0,0,0));
//Revert angle to normal for line segment. Invert Port/Starboard since it is an entry arrow.
Polygon entrySection = constructLineSegment(
roundingSide == RoundingSide.PORT ? RoundingSide.STARBOARD : RoundingSide.PORT, 180 + angleOfEntry, colour
roundingSide == RoundingSide.PORT ? RoundingSide.STARBOARD : RoundingSide.PORT,
180 + angleOfEntry, colour
);
arrow.getChildren().addAll(exitSection, roundSection, entrySection);
return arrow;
}
/**
* Make an arrow when the turning is not around the outside of the mark.
*
* @param roundingSide side to round on.
* @param angleOfExit angle of entry
* @param angleOfEntry angle of exit
* @param colour colour of arrow
* @return the arrow.
*/
private static Group makeInteriorAngle (RoundingSide roundingSide, double angleOfExit, double angleOfEntry, Paint colour) {
Group arrow = new Group();
Polygon lineSegment;
//Reverse angle of exit/entry to find position between them
angleOfEntry = Math.toRadians(360 - angleOfEntry);
angleOfExit = Math.toRadians(180 - angleOfExit);
//Find start of entry arrow if it was a regular arrow.
int multiplier = roundingSide == RoundingSide.STARBOARD ? -1 : 1;
double xStart = multiplier * MARK_ARROW_SEPARATION * Math.sin(angleOfEntry + Math.PI / 2);
double yStart = multiplier * MARK_ARROW_SEPARATION * Math.cos(angleOfEntry + Math.PI / 2);
xStart = xStart + (ARROW_LENGTH * Math.sin(angleOfEntry));
yStart = yStart + (ARROW_LENGTH * Math.cos(angleOfEntry));
//Find of end exit arrow if it was a regular arrow.
multiplier = roundingSide == RoundingSide.STARBOARD ? 1 : -1;
double xEnd = multiplier * MARK_ARROW_SEPARATION * Math.sin(angleOfExit + Math.PI / 2);
double yEnd = multiplier * MARK_ARROW_SEPARATION * Math.cos(angleOfExit + Math.PI / 2);
xEnd = xEnd + (ARROW_LENGTH * Math.sin(angleOfExit));
yEnd = yEnd + (ARROW_LENGTH * Math.cos(angleOfExit));
//Make line between these points.
lineSegment = new Polygon(
xStart, yStart,
xEnd, yEnd
@@ -85,12 +159,14 @@ public class MarkArrowFactory {
lineSegment.setFill(Color.BLUE);
lineSegment.setStrokeWidth(STROKE_WIDTH);
lineSegment.setStrokeLineCap(StrokeLineCap.ROUND);
//Make arrow head at the angle between these points.
Polyline arrowHead = constructArrowHead(
90 + Math.toDegrees(Math.atan2(yStart - yEnd, xEnd - xStart)),
colour
);
arrowHead.setLayoutX(xEnd);
arrowHead.setLayoutY(yEnd);
//Construct arrow.
arrow.getChildren().addAll(lineSegment, arrowHead);
return arrow;
}
@@ -113,6 +189,15 @@ public class MarkArrowFactory {
return arrow;
}
/**
* Constructs a line rotated to the correct angle and and in the correct position for a mark at
* position 0,0. Note that a line segment is assumed to be facing away from the mark so for
* entry Starboard make the RoundingSide Port and vice versa.
* @param roundingSide Rounding side of an exit arrow. (Reversed for entry)
* @param angle Angle of line segment.
* @param colour the desired colour of the line.
* @return Line segmented at correct rotation centered at (0,0)
*/
private static Polygon constructLineSegment (RoundingSide roundingSide, double angle, Paint colour) {
Polygon lineSegment;
angle = Math.toRadians(angle);
@@ -122,8 +207,8 @@ public class MarkArrowFactory {
double xEnd = xStart + (ARROW_LENGTH * Math.sin(angle));
double yEnd = yStart + (ARROW_LENGTH * Math.cos(angle));
lineSegment = new Polygon(
xStart, yStart,
xEnd, yEnd
xStart, yStart,
xEnd, yEnd
);
lineSegment.setStroke(colour);
lineSegment.setFill(Color.BLUE);
@@ -132,6 +217,12 @@ public class MarkArrowFactory {
return lineSegment;
}
/**
* Constructs a PolyLine in the shape of an arrow head.
* @param rotation direction for the arrow head to point.
* @param colour colour of the arrow head
* @return the arrowhead shaped PolyLine.
*/
private static Polyline constructArrowHead (double rotation, Paint colour) {
Polyline arrow = new Polyline(
-ARROW_HEAD_WIDTH, -ARROW_HEAD_DEPTH,
@@ -1,460 +0,0 @@
package seng302.visualiser.fxObjects.assets_2D;
import javafx.application.Platform;
import javafx.beans.property.ReadOnlyDoubleWrapper;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.geometry.Point2D;
import javafx.geometry.Point3D;
import javafx.scene.AmbientLight;
import javafx.scene.Group;
import javafx.scene.Node;
import javafx.scene.PointLight;
import javafx.scene.paint.Color;
import javafx.scene.paint.PhongMaterial;
import javafx.scene.shape.Line;
import javafx.scene.shape.Polygon;
import javafx.scene.shape.Polyline;
import javafx.scene.shape.Shape3D;
import javafx.scene.transform.Rotate;
import javafx.scene.transform.Scale;
import javafx.scene.transform.Translate;
import seng302.visualiser.fxObjects.assets_3D.BoatMeshType;
import seng302.visualiser.fxObjects.assets_3D.BoatModel;
import seng302.visualiser.fxObjects.assets_3D.ModelFactory;
import seng302.visualiser.fxObjects.assets_3D.ModelType;
import java.util.ArrayList;
import java.util.List;
/**
* BoatGroup is a javafx group that by default contains a graphical objects for representing a 2
* dimensional boat. It contains a single polygon for the boat, a group of lines to show it's path,
* a wake object and two text labels to annotate the boat teams name and the boats velocity. The
* boat will update it's position onscreen everytime UpdatePosition is called unless the window is
* minimized in which case it attempts to store animations and apply them when the window is
* maximised.
*/
public class BoatObject extends Group {
private static final double MODEL_SCALE_FACTOR = 400;
private static final double MODEL_X_OFFSET = 0; // standard
private static final double MODEL_Y_OFFSET = 0; // standard
private static final int VIEWPORT_SIZE = 800;
private static final Color lightColor = Color.rgb(244, 255, 250);
private static final Color jewelColor = Color.rgb(0, 190, 222);
@FunctionalInterface
public interface SelectedBoatListener {
void notifySelected(BoatObject boatObject, Boolean isSelected);
}
//Constants for drawing
private static final float BOAT_HEIGHT = 15f;
private static final float BOAT_WIDTH = 10f;
private double xVelocity;
private double yVelocity;
private double lastHeading;
private double sailState;
//Graphical objects
private Polyline trail = new Polyline();
private BoatModel boatAssets;
private Polygon sail;
private Group wake;
private Line leftLayLine;
private Line rightLayline;
private double distanceTravelled, lastRotation;
private Point2D lastPoint;
private Color colour = Color.BLACK;
private Boolean isSelected = false, destinationSet; //All boats are initialised as selected
private boolean isPlayer = false;
private Rotate rotation = new Rotate(0,0,1);
private List<SelectedBoatListener> selectedBoatListenerListeners = new ArrayList<>();
/**
* Creates a BoatGroup with the default triangular boat polygon.
*/
public BoatObject() {
this(-BOAT_WIDTH / 2, BOAT_HEIGHT / 2,
0.0, -BOAT_HEIGHT / 2,
BOAT_WIDTH / 2, BOAT_HEIGHT / 2);
}
/**
* Creates a BoatGroup with the boat being the default polygon. The head of the boat should be
* at point (0,0).
*
* @param points An array of co-ordinates x1,y1,x2,y2,x3,y3... that will make up the boat
* polygon.
*/
public BoatObject(double... points) {
initChildren(points);
}
/**
* Creates the javafx objects that will be the in the group by default.
*
* @param points An array of co-ordinates x1,y1,x2,y2,x3,y3... that will make up the boat
* polygon.
*/
private void initChildren(double... points) {
boatAssets = ModelFactory.boatGameView(BoatMeshType.DINGHY, colour);
boatAssets.hideSail();
boatAssets.getAssets().getTransforms().addAll(
// new Rotate(-40, new Point3D(1,0,0)),
rotation
// new Rotate(-90, new Point3D(0,0,1))
);
boatAssets.getAssets().getTransforms().add(new Scale(5, 5, 5));
// boatAssets.setDrawMode(DrawMode.FILL);
// boatAssets.setFill(colour);
// boatAssets.setFill(this.colour);
// boatAssets.setMaterial(new PhongMaterial(this.colour));
boatAssets.getAssets().setOnMouseEntered(event -> {
// boatAssets.setFill(Color.FLORALWHITE);
// boatAssets.setStroke(Color.RED);
// boatAssets.setMaterial(new PhongMaterial(Color.FLORALWHITE));
});
boatAssets.getAssets().setOnMouseExited(event -> {
// boatAssets.setMaterial(new PhongMaterial(this.colour));
// boatAssets.setFill(colour);
// boatAssets.setFill(this.colour);
// boatAssets.setStroke(Color.BLACK);
});
boatAssets.getAssets().setOnMouseClicked(event -> setIsSelected(!isSelected));
boatAssets.getAssets().setCache(true);
// boatAssets.setCacheHint(CacheHint.SPEED);
// annotationBox = new AnnotationBox();
// annotationBox.setFill(colour);
leftLayLine = new Line();
rightLayline = new Line();
trail.getStrokeDashArray().setAll(5d, 10d);
trail.setCache(true);
wake = ModelFactory.importModel(ModelType.WAKE).getAssets();
sail = new Polygon(0.0,BOAT_HEIGHT / 4,
0.0, BOAT_HEIGHT);
sailState = 0;
sail.setStrokeWidth(2.0);
sail.setStroke(Color.BLACK);
sail.setFill(Color.TRANSPARENT);
sail.setCache(true);
super.getChildren().clear();
PointLight pointLight = new PointLight(Color.WHITE);
// pointLight.setLightOn(true);
pointLight.getTransforms().add(new Translate(0, 0, -30));
super.getChildren().add(pointLight);
// pointLight = new PointLight(Color.WHITE);
//// pointLight.setLightOn(true);
// pointLight.getTransforms().add(new Translate(50, 50, -20));
// super.getChildren().add(pointLight);
// pointLight = new PointLight(Color.WHITE);
//// pointLight.setLightOn(true);
// pointLight.getTransforms().add(new Translate(50, -50, -20));
// super.getChildren().add(pointLight);
// pointLight = new PointLight(Color.WHITE);
//// pointLight.setLightOn(true);
// pointLight.getTransforms().add(new Translate(-50, -50, -20));
// super.getChildren().add(pointLight);
AmbientLight light = new AmbientLight(new Color(0.5,0.5,0.5,1));
super.getChildren().add(light);
super.getChildren().addAll(boatAssets.getAssets());
}
public void setFill (Color value) {
this.colour = value;
PhongMaterial pm = new PhongMaterial(this.colour);
pm.setSpecularPower(0.5);
pm.setSpecularColor(Color.BLACK);
for (int i=0;i<2;i++) {
Shape3D s = (Shape3D) boatAssets.getAssets().getChildren().get(i);
s.setMaterial(pm);
}
trail.setStroke(colour);
}
/**
* Moves the boat and its children annotations to coordinates specified
* @param x The X coordinate to move the boat to
* @param y The Y coordinate to move the boat to
* @param rotation The rotation by which the boat moves
* @param velocity The velocity the boat is moving
* @param sailIn Boolean to toggle sail state.
* @param windDir .
*/
public void moveTo(double x, double y, double rotation, double velocity, Boolean sailIn, double windDir) {
Double dx = Math.abs(boatAssets.getAssets().getLayoutX() - x);
Double dy = Math.abs(boatAssets.getAssets().getLayoutY() - y);
Platform.runLater(() -> {
rotateTo(rotation, sailIn, windDir);
boatAssets.getAssets().setLayoutX(x);
boatAssets.getAssets().setLayoutY(y);
// if (sailIn) {
//// sail.getPoints().clear();
//// sail.getPoints().addAll(0.0, 0.0, 4.0, 1.5, 8.0, 3.0, 12.0, 3.5, 16.0, 3.0, 20.0, 1.5, 24.0, 0.0);
//// sail.getPoints().addAll(0.0, 0.0, 24.0, 0.0);
// sail.setLayoutX(x);
// sail.setLayoutY(y);
// } else {
//// animateSail();
// sail.setLayoutX(x);
// sail.setLayoutY(y);
// }
wake.setLayoutX(x);
wake.setLayoutY(y);
});
// wake.setRotation(rotation, velocity);
// rotateTo(rotation);
// boatAssets.setLayoutX(x);
// boatAssets.setLayoutY(y);
// wake.setLayoutX(x);
// wake.setLayoutY(y);
// wake.rotate(rotation);
// wake.setRotation(rotation, groundSpeed);
// isStopped = false;
// destinationSet = true;
lastRotation = rotation;
distanceTravelled += Math.sqrt((dx * dx) + (dy * dy));
if (distanceTravelled > 15 && isPlayer) {
distanceTravelled = 0d;
Platform.runLater(() -> trail.getPoints().addAll(x, y));
}
}
private Double normalizeHeading(double heading, double windDirection) {
Double normalizedHeading = heading - windDirection;
normalizedHeading = (double) Math.floorMod(normalizedHeading.longValue(), 360L);
return normalizedHeading;
}
private void rotateTo(double heading, boolean sailsIn, double windDir) {
rotation.setAngle(heading);
wake.getTransforms().setAll(new Rotate(heading, new Point3D(0,0,1)));
if (sailsIn) {
boatAssets.showSail();
Double sailWindOffset = 30.0;
Double upwindAngleLimit = 15.0;
Double downwindAngleLimit = 10.0; //Upwind from normalised horizontal
Double normalizedHeading = normalizeHeading(heading, windDir);
if (normalizedHeading < 180) {
if (normalizedHeading < sailWindOffset + upwindAngleLimit){
boatAssets.rotateSail(-heading + 90 - upwindAngleLimit);
} else if (normalizedHeading > 90 + sailWindOffset){
boatAssets.rotateSail(-heading + downwindAngleLimit);
} else {
boatAssets.rotateSail(-heading + 90 + sailWindOffset);
}
} else {
// if (normalizedHeading > 360 - (sailWindOffset + upwindAngleLimit)){
// boatAssets.rotateSail(-heading + 90 + upwindAngleLimit);
// } else if (normalizedHeading < 270 - sailWindOffset){
// boatAssets.rotateSail(-heading + 180 - downwindAngleLimit);
// } else {
// boatAssets.rotateSail(-heading + 90 - sailWindOffset);
// }
}
} else {
boatAssets.hideSail();
}
}
private void animateSail(){
Double[] points = new Double[200];
double amplitude = 2.0;
double period = 10;
for (int i = 0; i < 50; i++) {
points[i * 2] = amplitude * Math.sin(((Math.PI * i) / period + sailState));
points[i * 2 + 1] = (double) (BOAT_HEIGHT * i) / BOAT_HEIGHT / 2;
points[199 - (i * 2)] = (double) (BOAT_HEIGHT * i) / BOAT_HEIGHT / 2;
points[199 - (i * 2 + 1)] = amplitude * Math.sin(((Math.PI * i) / period + sailState));
}
if (sailState == - 2 * Math.PI) {
sailState = 0;
} else {
sailState = sailState - Math.PI / 5;
}
sail.getPoints().clear();
sail.getPoints().addAll(points);
}
public void updateLocation() {
// boatAssets.getTransforms().add(new Rotate(2, new Point3D(1,1,1)));
// double dx = xVelocity / 60;
// double dy = yVelocity / 60;
//
// distanceTravelled += Math.abs(dx) + Math.abs(dy);
// moveGroupBy(dx, dy);
//
// if (distanceTravelled > 70) {
// distanceTravelled = 0d;
//
// if (lastPoint != null) {
// Line l = new Line(
// lastPoint.getX(),
// lastPoint.getY(),
// boatAssets.getLayoutX(),
// boatAssets.getLayoutY()
// );
// l.getStrokeDashArray().setAll(3d, 7d);
// l.setStroke(colour);
// l.setCache(true);
// l.setCacheHint(CacheHint.SPEED);
// lineGroup.getChildren().add(l);
// }
// lastPoint = new Point2D(boatAssets.getLayoutX(), boatAssets.getLayoutY());
// }
// wake.updatePosition();
}
// /**
// * This function works out if a boat is going upwind or down wind. It looks at the boats current position, the next
// * gates position and the current wind
// * If bot the wind vector from the next gate and the boat from the next gate lay on the same side, then the boat is
// * going up wind, if they are on different sides of the gate, then the boat is going downwind
// * @param canvasController
// */
// public Boolean isUpwindLeg(GameViewController canvasController, Mark nextMark) {
//
// Double windAngle = StreamParser.getWindDirection();
// GateMark thisGateMark = (GateMark) nextMark;
// SingleMark nextMark1 = thisGateMark.getSingleMark1();
// SingleMark nextMark2 = thisGateMark.getSingleMark2();
// Point2D nextMarkPoint1 = canvasController.findScaledXY(nextMark1.getLatitude(), nextMark1.getLongitude());
// Point2D nextMarkPoint2 = canvasController.findScaledXY(nextMark2.getLatitude(), nextMark2.getLongitude());
//
// Point2D boatCurrentPoint = new Point2D(boatAssets.getLayoutX(), boatAssets.getLayoutY());
// Point2D windTestPoint = GeoUtility.makeArbitraryVectorPoint(nextMarkPoint1, windAngle, 10d);
//
//
// Integer boatLineFuncResult = GeoUtility.lineFunction(nextMarkPoint1, nextMarkPoint2, boatCurrentPoint);
// Integer windLineFuncResult = GeoUtility.lineFunction(nextMarkPoint1, nextMarkPoint2, windTestPoint);
//
//
// /*
// If both the wind vector from the gate and the boat from the gate are on the same side of that gate, then the
// boat is travelling into the wind. thus upwind. Otherwise if they are on different sides, then the boat is going
// with the wind.
// */
// return boatLineFuncResult.equals(windLineFuncResult);
// return true;
// }
public void setIsSelected(Boolean isSelected) {
updateListener(isSelected);
this.isSelected = isSelected;
setLineGroupVisible(isSelected);
setWakeVisible(isSelected);
setLayLinesVisible(isSelected);
}
public void setVisibility (boolean teamName, boolean velocity, boolean estTime, boolean legTime,
boolean trail, boolean wake) {
// boatAnnotations.setVisible(teamName, velocity, estTime, legTime);
// this.wake.setVisible(wake);
this.trail.setVisible(trail);
}
public void setLineGroupVisible(Boolean visible) {
trail.setVisible(visible);
}
public void setWakeVisible(Boolean visible) {
// wake.setVisible(visible);
}
public void setLayLinesVisible(Boolean visible) {
leftLayLine.setVisible(visible);
rightLayline.setVisible(visible);
}
public void setLaylines(Line line1, Line line2) {
this.leftLayLine = line1;
this.rightLayline = line2;
}
public ArrayList<Line> getLaylines() {
ArrayList<Line> laylines = new ArrayList<>();
laylines.add(leftLayLine);
laylines.add(rightLayline);
return laylines;
}
public Group getWake () {
return wake;
}
public Node getTrail() {
return trail;
}
public Double getBoatLayoutX() {
return boatAssets.getAssets().getLayoutX();
}
public Double getBoatLayoutY() {
return boatAssets.getAssets().getLayoutY();
}
/**
* Sets this boat to appear highlighted
*/
public void setAsPlayer() {
// boatAssets.getPoints().setAll(
// -BOAT_WIDTH / 1.75, BOAT_HEIGHT / 1.75,
// 0.0, -BOAT_HEIGHT / 1.75,
// BOAT_WIDTH / 1.75, BOAT_HEIGHT / 1.75
// );
// boatAssets.setStroke(Color.BLACK);
// boatAssets.setStrokeWidth(2);
// boatAssets.setStrokeLineCap(StrokeLineCap.ROUND);
isPlayer = true;
animateSail();
}
public void setTrajectory(double heading, double velocity, double windDir) {
// wake.r(lastHeading - heading, velocity);
// rotateTo(heading, false, windDir);
xVelocity = Math.cos(Math.toRadians(heading)) * velocity;
yVelocity = Math.sin(Math.toRadians(heading)) * velocity;
lastHeading = heading;
}
public Boolean getSelected() {
return isSelected;
}
public void setTrajectory(double heading, double velocity, double scaleFactorX, double scaleFactorY) {
// wake.setRotation(lastHeading - heading, velocity);
// rotateTo(heading);
// xVelocity = Math.cos(Math.toRadians(heading)) * velocity * scaleFactorX;
// yVelocity = Math.sin(Math.toRadians(heading)) * velocity * scaleFactorY;
lastHeading = heading;
}
private void updateListener(Boolean isSelected) {
for (SelectedBoatListener sbl : selectedBoatListenerListeners) {
sbl.notifySelected(this, isSelected);
}
}
public void addSelectedBoatListener(SelectedBoatListener sbl) {
selectedBoatListenerListeners.add(sbl);
}
}
@@ -7,6 +7,7 @@ import javafx.scene.Group;
import javafx.scene.paint.Color;
import javafx.scene.paint.Paint;
import javafx.scene.shape.Circle;
import seng302.visualiser.fxObjects.MarkArrowFactory;
/**
* Visual object for a mark. Contains a coloured circle and any specified arrows.
@@ -2,21 +2,59 @@ package seng302.visualiser.fxObjects.assets_3D;
/**
* Enum for boat meshes. Enum values should be of the form :
* ENUM_VALUE (hull file, mast file, X offset of mast CoR from origin, sail file, X offset of sail CoR from origin)
* ENUM_VALUE (hull file, mast file, Y offset of mast CoR from origin, sail file, Y offset of sail CoR from origin, jib file, fixed sail)
* Files must be valid .stl files.
*/
public enum BoatMeshType {
DINGHY ("dinghy_hull.stl", "dinghy_mast.stl", -1.36653, "dinghy_sail.stl", -1.36653);
DINGHY("dinghy_hull.stl", "dinghy_mast.stl", 1.36653, "dinghy_sail.stl", 1.36653, null, false, 1.8, 1.0, 1.0),
CATAMARAN("catamaran_hull.stl", "catamaran_mast.stl", 0.997, "catamaran_sail.stl",
0.997, null, false, 1.0, 1.4, 2.0),
PIRATE_SHIP("pirateship_hull.stl", "pirateship_mast.stl", -0.5415, "pirateship_mainsail.stl",
-0.5415, "pirateship_frontsail.stl", true, 1.2, 1.6, 1.2);
final String hullFile, mastFile, sailFile;
final String hullFile, mastFile, sailFile, jibFile;
final double mastOffset, sailOffset;
public final double maxSpeedMultiplier;
public final double accelerationMultiplier;
public final double turnStep;
final boolean fixedSail;
final static BoatMeshType[] boatTypes = new BoatMeshType[]{DINGHY, CATAMARAN, PIRATE_SHIP};
BoatMeshType(String hullFile, String mastFile, double mastOffset, String sailFile, double sailOffset) {
BoatMeshType(String hullFile, String mastFile, double mastOffset, String sailFile,
double sailOffset, String jibFile, boolean fixedSail, double maxSpeedMultiplier, double accelerationMultiplier, double turnStep) {
this.hullFile = hullFile;
this.mastFile = mastFile;
this.mastOffset = mastOffset;
this.sailFile = sailFile;
this.sailOffset = sailOffset;
this.jibFile = jibFile;
this.fixedSail = fixedSail;
this.maxSpeedMultiplier = maxSpeedMultiplier;
this.accelerationMultiplier = accelerationMultiplier;
this.turnStep = turnStep;
}
public static BoatMeshType getNextBoatType(BoatMeshType boatType) {
for (int i = 0; i < boatTypes.length; i++) {
if (i == boatTypes.length -1) {
return boatTypes[0];
} else if (boatType == boatTypes[i]) {
return boatTypes[i+1];
}
}
return boatType;
}
public static BoatMeshType getPrevBoatType(BoatMeshType boatType) {
for (int i = 0; i < boatTypes.length; i++) {
if (i == 0 && boatType == boatTypes[i]) {
return boatTypes[boatTypes.length -1];
} else if (boatType == boatTypes[i]) {
return boatTypes[i-1];
}
}
return boatType;
}
}
@@ -34,14 +34,16 @@ public class BoatModel extends Model {
* @param degrees The rotation of the sail in degrees
*/
public void rotateSail(double degrees) {
MeshView mast = getMeshViewChild(MAST_INDEX);
MeshView sail = getMeshViewChild(SAIL_INDEX);
mast.getTransforms().setAll(
new Rotate(degrees, -meshType.mastOffset, 0,0, new Point3D(0, 0, 1))
);
sail.getTransforms().setAll(
new Rotate(degrees, -meshType.sailOffset, 0,0, new Point3D(0, 0, 1))
);
if (!meshType.fixedSail) {
MeshView mast = getMeshViewChild(MAST_INDEX);
MeshView sail = getMeshViewChild(SAIL_INDEX);
mast.getTransforms().setAll(
new Rotate(degrees, 0, -meshType.mastOffset, 0, new Point3D(0, 0, 1))
);
sail.getTransforms().setAll(
new Rotate(degrees, 0, -meshType.sailOffset,0, new Point3D(0, 0, 1))
);
}
}
public void hideSail() {
@@ -58,7 +60,7 @@ public class BoatModel extends Model {
*/
public void changeColour(Color newColour) {
changeColourChild(HULL_INDEX, newColour);
changeColourChild(SAIL_INDEX, newColour);
changeColourChild(MAST_INDEX, newColour);
}
private void changeColourChild(int index, Color newColour) {
@@ -69,4 +71,8 @@ public class BoatModel extends Model {
private MeshView getMeshViewChild(int index) {
return (MeshView) assets.getChildren().get(index);
}
public BoatMeshType getMeshType() {
return meshType;
}
}
@@ -0,0 +1,142 @@
package seng302.visualiser.fxObjects.assets_3D;
import java.util.ArrayList;
import java.util.List;
import javafx.application.Platform;
import javafx.beans.property.ReadOnlyDoubleWrapper;
import javafx.geometry.Point3D;
import javafx.scene.Group;
import javafx.scene.paint.Color;
import javafx.scene.transform.Rotate;
/**
* BoatGroup is a javafx group that by default contains a graphical objects for representing a 2
* dimensional boat. It contains a single polygon for the boat, a group of lines to show it's path,
* a wake object and two text labels to annotate the boat teams name and the boatTypes velocity. The
* boat will update it's position onscreen everytime UpdatePosition is called unless the window is
* minimized in which case it attempts to store animations and apply them when the window is
* maximised.
*/
public class BoatObject extends Group {
@FunctionalInterface
public interface SelectedBoatListener {
void notifySelected(BoatObject boatObject, Boolean isSelected);
}
private BoatModel boatAssets;
private Group wake;
private Color colour = Color.BLACK;
private Boolean isSelected = false;
private Rotate rotation = new Rotate(0, new Point3D(0,0,1));
private ReadOnlyDoubleWrapper rotationProperty;
private List<SelectedBoatListener> selectedBoatListenerListeners = new ArrayList<>();
/**
* Creates a BoatGroup with the default triangular boat polygon.
*/
public BoatObject(BoatMeshType boatMeshType) {
rotationProperty = new ReadOnlyDoubleWrapper(0.0);
boatAssets = ModelFactory.boatGameView(boatMeshType, colour);
boatAssets.hideSail();
boatAssets.getAssets().getTransforms().addAll(
rotation
);
boatAssets.getAssets().setOnMouseClicked(event -> {
setIsSelected(!isSelected);
updateListeners();
});
boatAssets.getAssets().setCache(true);
wake = ModelFactory.importModel(ModelType.WAKE).getAssets();
super.getChildren().addAll(boatAssets.getAssets());
}
public void setFill (Color value) {
this.colour = value;
boatAssets.changeColour(colour);
}
/**
* Moves the boat and its children annotations to coordinates specified
* @param x The X coordinate to move the boat to
* @param y The Y coordinate to move the boat to
* @param rotation The rotation by which the boat moves
* @param velocity The velocity the boat is moving
* @param sailIn Boolean to toggle sail state.
* @param windDir .
*/
public void moveTo(double x, double y, double rotation, double velocity, Boolean sailIn, double windDir) {
Platform.runLater(() -> {
rotateTo(rotation, sailIn, windDir);
this.layoutXProperty().setValue(x);
this.layoutYProperty().setValue(y);
wake.setLayoutX(x);
wake.setLayoutY(y);
});
}
private Double normalizeHeading(double heading, double windDirection) {
Double normalizedHeading = heading - windDirection;
normalizedHeading = (double) Math.floorMod(normalizedHeading.longValue(), 360L);
return normalizedHeading;
}
private void rotateTo(double heading, boolean sailsIn, double windDir) {
rotationProperty.set(heading);
rotation.setAngle(heading);
wake.getTransforms().setAll(new Rotate(heading, new Point3D(0,0,1)));
if (sailsIn) {
boatAssets.showSail();
Double sailWindOffset = 30.0;
Double upwindAngleLimit = 15.0;
Double downwindAngleLimit = 10.0; //Upwind from normalised horizontal
Double normalizedHeading = normalizeHeading(heading, windDir);
if (normalizedHeading < 180) {
if (normalizedHeading < sailWindOffset + upwindAngleLimit){
boatAssets.rotateSail(-upwindAngleLimit);
} else if (normalizedHeading > 90 + sailWindOffset){
boatAssets.rotateSail(-90 + downwindAngleLimit);
} else {
boatAssets.rotateSail(-heading + windDir + sailWindOffset);
}
} else {
if (normalizedHeading > 360 - (sailWindOffset + upwindAngleLimit)) {
boatAssets.rotateSail(upwindAngleLimit);
} else if (normalizedHeading < 270 - sailWindOffset) {
boatAssets.rotateSail(90 - downwindAngleLimit);
} else {
boatAssets.rotateSail(-heading + windDir - sailWindOffset);
}
}
} else {
boatAssets.hideSail();
}
}
public Group getWake () {
return wake;
}
public void setIsSelected(Boolean isSelected) {
this.isSelected = isSelected;
}
private void updateListeners() {
for (SelectedBoatListener sbl : selectedBoatListenerListeners) {
sbl.notifySelected(this, this.isSelected);
}
}
public void addSelectedBoatListener(SelectedBoatListener sbl) {
selectedBoatListenerListeners.add(sbl);
}
public ReadOnlyDoubleWrapper getRotationProperty() {
return rotationProperty;
}
}
@@ -4,45 +4,42 @@ import java.util.ArrayList;
import java.util.List;
import javafx.application.Platform;
import javafx.scene.Group;
import javafx.scene.paint.Color;
import javafx.scene.paint.Paint;
import javafx.scene.transform.Scale;
import seng302.visualiser.fxObjects.assets_2D.MarkArrowFactory;
import seng302.visualiser.fxObjects.assets_2D.MarkArrowFactory.RoundingSide;
import seng302.visualiser.fxObjects.MarkArrowFactory;
import seng302.visualiser.fxObjects.MarkArrowFactory.RoundingSide;
/**
* Visual object for a mark. Contains a coloured circle and any specified arrows.
*/
public class Marker3D extends Group {
private Group mark = ModelFactory.importModel(ModelType.PLAIN_MARKER).getAssets();
private Paint colour = Color.BLACK;
private Model mark;
private List<Group> enterArrows = new ArrayList<>();
private List<Group> exitArrows = new ArrayList<>();
private int enterArrowIndex = 0;
private int exitArrowIndex = 0;
private ModelType markType;
private ModelType arrowType;
/**
* Creates a new Marker containing only a circle. The default colour is black.
*/
public Marker3D() {
// mark.setRadius(5);
// mark.setCenterX(0);
// mark.setCenterY(0);
Platform.runLater(() -> {
mark.getTransforms().add(new Scale(5,5,5));
this.getChildren().addAll(mark, new Group());
}); //Empty group placeholder or arrows.
}
/**
* Creates a new Marker containing only a circle of the given colour.
* @param colour the desired colour for the marker.
*/
public Marker3D(Paint colour) {
this();
this.colour = colour;
// mark.setFill(colour);
public Marker3D(ModelType modelType) {
markType = modelType;
switch (markType) {
case PLAIN_MARKER:
arrowType = ModelType.PLAIN_ARROW;
break;
case FINISH_MARKER:
arrowType = ModelType.FINISH_ARROW;
break;
case START_MARKER:
arrowType = ModelType.START_ARROW;
break;
}
mark = ModelFactory.importModel(modelType);
Platform.runLater(() ->
this.getChildren().addAll(mark.getAssets())
);
}
/**
@@ -53,13 +50,13 @@ public class Marker3D extends Group {
* @param exitAngle The angle the arrow wil point from the marker.
*/
public void addArrows(RoundingSide roundingSide, double entryAngle,
double exitAngle) {
double exitAngle) {
//Change Color.GRAY to this.colour to revert all gray arrows.
enterArrows.add(
MarkArrowFactory.constructEntryArrow(roundingSide, entryAngle, exitAngle, Color.GRAY)
MarkArrowFactory.constructEntryArrow3D(roundingSide, entryAngle, arrowType).getAssets()
);
exitArrows.add(
MarkArrowFactory.constructExitArrow(roundingSide, exitAngle, Color.GRAY)
MarkArrowFactory.constructExitArrow3D(roundingSide, exitAngle, arrowType).getAssets()
);
}
@@ -81,12 +78,9 @@ public class Marker3D extends Group {
private void showArrow(List<Group> arrowList, int arrowListIndex) {
if (arrowListIndex < arrowList.size()) {
if (arrowListIndex == 1) {;
}
Platform.runLater(() -> {
this.getChildren().remove(1);
this.getChildren().add(arrowList.get(arrowListIndex));
});
Platform.runLater(() ->
this.getChildren().setAll(mark.getAssets(), arrowList.get(arrowListIndex))
);
}
}
@@ -94,6 +88,6 @@ public class Marker3D extends Group {
* Hides all arrows.
*/
public void hideAllArrows() {
Platform.runLater(() -> this.getChildren().setAll(mark, new Group()));
Platform.runLater(() -> this.getChildren().setAll(mark.getAssets()));
}
}
@@ -8,10 +8,10 @@ import javafx.scene.Group;
*/
public class Model {
AnimationTimer animationTimer;
Group assets;
protected AnimationTimer animationTimer;
protected Group assets;
Model (Group assets, AnimationTimer animation) {
public Model (Group assets, AnimationTimer animation) {
this.assets = assets;
this.animationTimer = animation;
if (animation != null) {
@@ -25,7 +25,7 @@ public class Model {
}
}
public void setAnimation(AnimationTimer animation) {
void setAnimation(AnimationTimer animation) {
animationTimer = animation;
if (animation != null) {
animation.start();
@@ -7,8 +7,10 @@ import javafx.geometry.Point3D;
import javafx.scene.AmbientLight;
import javafx.scene.CacheHint;
import javafx.scene.Group;
import javafx.scene.PointLight;
import javafx.scene.paint.Color;
import javafx.scene.paint.PhongMaterial;
import javafx.scene.shape.Circle;
import javafx.scene.shape.MeshView;
import javafx.scene.transform.Rotate;
import javafx.scene.transform.Scale;
@@ -17,7 +19,7 @@ import javafx.scene.transform.Translate;
/**
* Factory class for creating 3D models of boats.
* Factory class for creating 3D models of boatTypes.
*/
public class ModelFactory {
@@ -50,6 +52,35 @@ public class ModelFactory {
return bo;
}
public static BoatModel boatCustomiseView(BoatMeshType boatType, Color primaryColour) {
Group boatAssets = getUnmodifiedBoatModel(boatType, primaryColour);
final Rotate animationRotate = new Rotate(0, new Point3D(0,0,1));
boatAssets.getTransforms().addAll(
new Scale(8.0, 8.0, 8.0),
new Rotate(-70, new Point3D(1,0,0)),
new Translate(16,50, 1),
animationRotate
);
boatAssets.getTransforms().add(animationRotate);
BoatModel bo = new BoatModel(boatAssets, null, boatType);
bo.rotateSail(45);
bo.setAnimation(new AnimationTimer() {
double boatAngle = 0;
Rotate rotate = animationRotate;
@Override
public void handle(long now) {
boatAngle += 0.5;
rotate.setAngle(boatAngle);
}
});
boatAssets.getChildren().addAll(
new AmbientLight()
);
return bo;
}
public static BoatModel boatRotatingView(BoatMeshType boatType, Color primaryColour) {
Group boatAssets = getUnmodifiedBoatModel(boatType, primaryColour);
boatAssets.getTransforms().addAll(
@@ -77,27 +108,35 @@ public class ModelFactory {
public static BoatModel boatGameView(BoatMeshType boatType, Color primaryColour) {
Group boatAssets = getUnmodifiedBoatModel(boatType, primaryColour);
boatAssets.getTransforms().setAll(
new Rotate(-90, new Point3D(0,0,1)),
new Scale(0.06, 0.06, 0.06)
new Scale(0.3, 0.3, 0.3)
);
return new BoatModel(boatAssets, null, boatType);
}
private static Group getUnmodifiedBoatModel(BoatMeshType boatType, Color primaryColour) {
Group boatAssets = new Group();
MeshView hull = importFile(boatType.hullFile);
MeshView hull = importSTL(boatType.hullFile);
hull.setMaterial(new PhongMaterial(primaryColour));
MeshView mast = importFile(boatType.mastFile);
MeshView mast = importSTL(boatType.mastFile);
mast.setMaterial(new PhongMaterial(primaryColour));
MeshView sail = importFile(boatType.sailFile);
MeshView sail = importSTL(boatType.sailFile);
sail.setMaterial(new PhongMaterial(Color.WHITE));
boatAssets.getChildren().addAll(hull, mast, sail);
if (boatType.jibFile != null) {
MeshView jib = importSTL(boatType.jibFile);
sail.setMaterial(new PhongMaterial(Color.WHITE));
boatAssets.getChildren().addAll(hull, mast, sail, jib);
} else {
boatAssets.getChildren().addAll(hull, mast, sail);
}
return boatAssets;
}
private static MeshView importFile(String fileName) {
private static MeshView importSTL(String fileName) {
StlMeshImporter importer = new StlMeshImporter();
importer.read(ModelFactory.class.getResource("/meshes/" + fileName));
importer.read(ModelFactory.class.getResource("/meshes/boatSTLs/" + fileName));
MeshView importedFile = new MeshView(importer.getImport());
importedFile.setCache(true);
importedFile.setCacheHint(CacheHint.SCALE_AND_ROTATE);
@@ -137,6 +176,10 @@ public class ModelFactory {
return makeTrail(assets);
case PLAYER_IDENTIFIER:
return makeIdentifierIcon(assets);
case START_ARROW:
case FINISH_ARROW:
case PLAIN_ARROW:
makeArrow(assets);
default:
return new Model(new Group(assets), null);
}
@@ -174,23 +217,17 @@ public class ModelFactory {
}
private static Model makeOcean(Group group) {
// group.setScaleY(Double.MAX_VALUE);
// group.setScaleX(Double.MAX_VALUE);
group.getTransforms().addAll(
new Rotate(90, new Point3D(1, 0, 0)),
new Scale(10,4,10)
Circle ocean = new Circle(
0,0,250, Color.SKYBLUE
);
// group.getChildren().add(new AmbientLight());
// Circle ocean = new Circle(0,0,500, Color.SKYBLUE);
// ocean.setStroke(Color.TRANSPARENT);
// group.getChildren().add(ocean);
ocean.setStroke(Color.TRANSPARENT);
group.getChildren().add(ocean);
return new Model(new Group(group), null);
}
private static Model makeBarrier(Group assets) {
assets.getTransforms().addAll(
new Rotate(90, new Point3D(1,0,0)),
new Scale(1.5,1.5,1.5)
new Rotate(90, new Point3D(1,0,0))
);
return new Model(new Group(assets), null);
}
@@ -213,7 +250,8 @@ public class ModelFactory {
private static Model makeTrail(Group trailPiece) {
trailPiece.getTransforms().addAll(
new Rotate(90, new Point3D(0,0,1))
new Rotate(-90, new Point3D(0,0,1)),
new Rotate(90, new Point3D(1,0,0))
);
return new Model(new Group(trailPiece), null);
}
@@ -225,4 +263,11 @@ public class ModelFactory {
);
return new Model(assets, null);
}
private static Model makeArrow(Group assets) {
assets.getTransforms().addAll(
new Rotate(90, new Point3D(1,0,0))
);
return new Model(new Group(assets), null);
}
}
@@ -11,7 +11,7 @@ public enum ModelType {
START_MARKER ("start_marker.dae"),
PLAIN_MARKER ("plain_marker.dae"),
MARK_AREA ("mark_area.dae"),
OCEAN ("ocean.dae"),
OCEAN (null),
BORDER_PYLON ("barrier_pole.dae"),
BORDER_BARRIER ("barrier_segment.dae"),
FINISH_LINE ("finish_line.dae"),
@@ -19,12 +19,14 @@ public enum ModelType {
GATE_LINE ("gate_line.dae"),
WAKE ("wake.dae"),
TRAIL_SEGMENT ("trail_segment.dae"),
PLAYER_IDENTIFIER ("player_identifier.dae");
PLAYER_IDENTIFIER ("player_identifier.dae"),
PLAIN_ARROW ("arrow.dae"),
START_ARROW ("start_arrow.dae"),
FINISH_ARROW ("finish_arrow.dae");
final String filename;
ModelType(String filename) {
this.filename = filename;
}
}
}
@@ -1,108 +0,0 @@
package seng302.visualiser;
import com.interactivemesh.jfx.importer.stl.StlMeshImporter;
import javafx.animation.AnimationTimer;
import javafx.application.Application;
import javafx.geometry.Point3D;
import javafx.scene.Camera;
import javafx.scene.Group;
import javafx.scene.PerspectiveCamera;
import javafx.scene.Scene;
import javafx.scene.SceneAntialiasing;
import javafx.scene.paint.Color;
import javafx.scene.paint.PhongMaterial;
import javafx.scene.shape.MeshView;
import javafx.scene.transform.Rotate;
import javafx.scene.transform.Scale;
import javafx.scene.transform.Translate;
import javafx.stage.Stage;
/**
* Created by cir27 on 7/09/17.
*/
public class test3d extends Application {
Group root = new Group();
Scene scene;
@Override
public void start(Stage primaryStage) throws Exception {
// camera = new PerspectiveCamera();
// gameObjects = new Group();
// root3D = new Group(camera, gameObjects);
scene = new Scene(
root, 1000, 1000, true, SceneAntialiasing.BALANCED
);
gameView3DTest();
primaryStage.setScene(scene);
primaryStage.show();
// scene.setCamera(camera);
// primaryStage.setScene(scene);
// primaryStage.show();
//
// StlMeshImporter importer = new StlMeshImporter();
// importer.read(test3d.class.getResource("/meshes/dinghy_hull.stl").toString());
// MeshView boat = new MeshView(importer.getImport());
// boat.setMaterial(new PhongMaterial(Color.GREENYELLOW));
//
// importer = new StlMeshImporter();
// importer.read(getClass().getResource("/meshes/dinghy_mast.stl").toString());
// MeshView mast = new MeshView(importer.getImport());
// mast.setMaterial(new PhongMaterial(Color.GREENYELLOW));
//
// importer = new StlMeshImporter();
// importer.read(getClass().getResource("/meshes/dinghy_sail.stl").toString());
// MeshView sail = new MeshView(importer.getImport());
// sail.setMaterial(new PhongMaterial(Color.LIGHTGREY));
//
// gameObjects.getChildren().addAll(boat, mast, sail);
//
// gameObjects.getTransforms().add(new Scale(25, 25,25));
// gameObjects.getTransforms().add(new Translate(15, 20,0));
// gameObjects.getTransforms().addAll(
// new Rotate(90, new Point3D(0,0,1)),
// new Rotate(90, new Point3D(0, 1, 0))
// );
//
//// PointLight light = new PointLight();
//// light.setLightOn(true);
//// light.getTransforms().add(new Translate(15, 20, 0));
////
//// PointLight light2 = new PointLight();
//// light2.setLightOn(true);
//// light2.getTransforms().add(new Translate(30, 40, 0));
//
//// root3D.getChildren().addAll(light);
//
// scene.setOnKeyPressed(event -> {
// switch (event.getCode()) {
// case UP:
// gameObjects.getTransforms().add(new Rotate(5, new Point3D(0,0,1)));
// break;
// case DOWN:
// gameObjects.getTransforms().add(new Rotate(-5, new Point3D(0,0,1)));
// break;
// case LEFT:
// gameObjects.getTransforms().add(new Rotate(-5, new Point3D(0,1,0)));
// break;
// case RIGHT:
// gameObjects.getTransforms().add(new Rotate(5, new Point3D(0,1,0)));
// break;
// }
// });
//
// AnimationTimer animationTimer = new AnimationTimer() {
// @Override
// public void handle(long now) {
// sail.getTransforms().add(new Rotate(0.5, 0, -1.36653, 0, new Point3D(0, 0, 1)));
// }
// };
//
//// animationTimer.start();
}
private void gameView3DTest() {
GameView3D gameView3D = new GameView3D();
root.getChildren().add(gameView3D.getAssets());
}
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

After

Width:  |  Height:  |  Size: 92 KiB

-9
View File
@@ -1,9 +0,0 @@
<?xml version="1.0" ?>
<configurations>
<race-name>AC35</race-name>
<race-size>6</race-size>
<time-scale>10.0</time-scale>
<windDir-direction>135</windDir-direction>
</configurations>
-80
View File
@@ -1,80 +0,0 @@
<?xml version="1.0" ?>
<markers>
<marks>
<gate>
<name type="start-line">Start</name>
<mark>
<name>Start1</name>
<latitude>57.6703330</latitude>
<longitude>11.8278330</longitude>
<id>122</id>
</mark>
<mark>
<name>Start2</name>
<latitude>57.6703330</latitude>
<longitude>11.8271333</longitude>
<id>123</id>
</mark>
</gate>
<mark>
<name>Mid Mark</name>
<latitude>57.6675700</latitude>
<longitude>11.8359880</longitude>
<id>131</id>
</mark>
<gate>
<name>Leeward Gate</name>
<mark>
<name>Leeward Gate1</name>
<latitude>57.6708220</latitude>
<longitude>11.8433900</longitude>
<id>124</id>
</mark>
<mark>
<name>Leeward Gate2</name>
<latitude>57.6711220</latitude>
<longitude>11.8436900</longitude>
<id>125</id>
</mark>
</gate>
<gate>
<name>Windward Gate</name>
<mark>
<name>Windward Gate1</name>
<latitude>57.6650170</latitude>
<longitude>11.8279170</longitude>
<id>126</id>
</mark>
<mark>
<name>Windward Gate2</name>
<latitude>57.6653170</latitude>
<longitude>11.8282170</longitude>
<id>127</id>
</mark>
</gate>
<gate type="finish-line">
<name>Finish</name>
<mark>
<name>Finish1</name>
<latitude>57.6715240</latitude>
<longitude>11.8444950</longitude>
<id>128</id>
</mark>
<mark>
<name>Finish2</name>
<latitude>57.6718240</latitude>
<longitude>11.8447950</longitude>
<id>129</id>
</mark>
</gate>
</marks>
<order>
<one>Start</one>
<two>Mid Mark</two>
<three>Leeward Gate</three>
<four>Windward Gate</four>
<five>Leeward Gate</five>
<six>Finish</six>
</order>
</markers>
-72
View File
@@ -1,72 +0,0 @@
<?xml version="1.0" ?>
<course>
<marks>
<gate>
<name type="start-line">Start</name>
<mark>
<name>Start1</name>
<latitude>32.296577</latitude>
<longitude>-64.854304</longitude>
</mark>
<mark>
<name>Start2</name>
<latitude>32.293771</latitude>
<longitude>-64.855242</longitude>
</mark>
</gate>
<mark>
<name>Mid Mark</name>
<latitude>32.293039</latitude>
<longitude>-64.843983</longitude>
</mark>
<gate>
<name>Leeward Gate</name>
<mark>
<name>Leeward Gate1</name>
<latitude>32.284680</latitude>
<longitude>-64.850045</longitude>
</mark>
<mark>
<name>Leeward Gate2</name>
<latitude>32.280164</latitude>
<longitude>-64.847591</longitude>
</mark>
</gate>
<gate>
<name>Windward Gate</name>
<mark>
<name>Windward Gate1</name>
<latitude>32.309693</latitude>
<longitude>-64.835249</longitude>
</mark>
<mark>
<name>Windward Gate2</name>
<latitude>32.308046</latitude>
<longitude>-64.831785</longitude>
</mark>
</gate>
<gate type="finish-line">
<name>Finish</name>
<mark>
<name>Finish1</name>
<latitude>32.317379</latitude>
<longitude>-64.839291</longitude>
</mark>
<mark>
<name>Finish2</name>
<latitude>32.317257</latitude>
<longitude>-64.836260</longitude>
</mark>
</gate>
</marks>
<order>
<one>Start</one>
<two>Mid Mark</two>
<three>Leeward Gate</three>
<four>Windward Gate</four>
<five>Leeward Gate</five>
<six>Finish</six>
</order>
</course>
-40
View File
@@ -1,40 +0,0 @@
<?xml version="1.0" ?>
<teams>
<team>
<name>Oracle Team USA</name>
<alias>USA</alias>
<currentVelocity>0.0</currentVelocity>
<id>102</id>
</team>
<team>
<name>Artemis Racing</name>
<alias>ART</alias>
<currentVelocity>0.0</currentVelocity>
<id>101</id>
</team>
<team>
<name>Emirates Team New Zealand</name>
<alias>NZL</alias>
<currentVelocity>0.0</currentVelocity>
<id>103</id>
</team>
<team>
<name>Land Rover BAR</name>
<alias>BAR</alias>
<currentVelocity>0.0</currentVelocity>
<id>104</id>
</team>
<team>
<name>SoftBank Team Japan</name>
<alias>JAP</alias>
<currentVelocity>0.0</currentVelocity>
<id>105</id>
</team>
<team>
<name>Groupama Team France</name>
<alias>FRC</alias>
<currentVelocity>0.0</currentVelocity>
<id>106</id>
</team>
</teams>
Binary file not shown.
+22
View File
@@ -44,8 +44,18 @@
-fx-border-color: -fx-decorator-color;
-fx-border-width: 0 4 4 4;
}
.jfx-decorator-button {
-fx-focus-traversable: false; /* so decorator button will not be focused */
}
/********* customised scroll bar for scroll pane ***********/
.scroll-pane {
-fx-focus-traversable: false;
-fx-border-style: none;
}
/* The main scrollbar **track** CSS class */
.scroll-bar:horizontal .track,
.scroll-bar:vertical .track {
@@ -99,4 +109,16 @@
.slider .track {
-fx-background-color: -fx-pp-dark-text-color;
}
.jfx-snackbar-content {
-fx-background-color: -fx-pp-front-color;
-fx-padding: 0 5 0 5;
-fx-spacing: 0 5 0 5;
-fx-font-size: 15;
}
.jfx-snackbar-toast {
-fx-text-fill: -fx-pp-theme-color;
-fx-font-size: 15;
}
+16 -6
View File
@@ -1,13 +1,15 @@
@font-face {
src: url("DJB-Get-Digital.ttf");
src: url("digital-7-mono.ttf");
}
#timerGrid{
-fx-background-color: rgba(255, 255, 255, 0.6);
-fx-effect: -fx-pp-dropshadow-light;
}
.timer Label {
-fx-font-family: "DJB Get Digital" !important;
GridPane .timer * {
-fx-font-family: "Digital-7 Mono" !important;
-fx-font-size: 30;
}
#timerLabel{
@@ -19,19 +21,26 @@
}
#chatGridPane {
-fx-background-color: rgba(255, 255, 255, 0.6);
-fx-background-color: transparent;
}
#chatHistoryHolder {
-fx-background-color: rgba(255, 255, 255, 0.6);
-fx-effect: -fx-pp-dropshadow-light;
}
#chatInputHolder {
-fx-background-color: rgba(255, 255, 255, 0.6);
-fx-effect: -fx-pp-dropshadow-light;
}
#windGridPane {
-fx-background-color: rgba(255, 255, 255, 0.6);
-fx-effect: -fx-pp-dropshadow-light;
}
#windHolder {
-fx-background-color: -fx-pp-front-color;
-fx-background-color: rgba(255, 255, 255, 0.5);
}
#chatSend {
@@ -39,6 +48,7 @@
-fx-text-fill: -fx-pp-theme-color;
-fx-font-size: 13px;
-fx-pref-height: 35px;
-fx-focus-traversable: false;
}
#chatSend:hover {
@@ -52,5 +62,5 @@
}
#windImageView {
-fx-image: url("/images/wind.png");
-fx-image: url("/images/wind-180.png");
}
@@ -2,6 +2,7 @@
-fx-font-size: 20px;
-fx-text-fill: -fx-pp-light-text-color;
-fx-background-color: -fx-pp-theme-color;
-fx-focus-traversable: false;
}
.jfx-rippler {
@@ -1,6 +1,3 @@
* {
-fx-text-fill: -fx-pp-dark-text-color;
}
#submitBtn {
-fx-background-color: -fx-pp-theme-color;
@@ -23,7 +20,15 @@
-fx-font-size: 18px;
}
#boatName, #boatColorLabel, #hostDialogHeader {
-fx-text-fill: -fx-pp-dark-text-color;
}
#boatName {
-fx-font-size: 18px;
-fx-prompt-text-fill: -fx-pp-dark-text-color;
}
#boatName .error-label {
-fx-font-size: 13px;
}
@@ -0,0 +1,21 @@
#raceFinishLabel {
-fx-font-size: 23px !important;
-fx-text-fill: -fx-pp-dark-text-color;
}
#finishersList {
}
#playAgain {
-fx-background-color: -fx-pp-theme-color;
-fx-text-fill: -fx-pp-light-text-color;
-fx-font-size: 20px !important;
-fx-effect: -fx-pp-dropshadow-dark;
-fx-min-width: 130px;
}
#playAgain:hover {
-fx-font-size: 23px !important;
-fx-background-color: -fx-pp-light-theme-color;
}
@@ -0,0 +1,59 @@
#keyBindingDialogHeader {
-fx-font-size: 27px;
-fx-text-fill: -fx-pp-dark-text-color;
}
#closeLabel {
-fx-font-size: 30;
-fx-text-fill: -fx-pp-dark-text-color;
}
#closeLabel:hover {
-fx-text-fill: red;
-fx-font-size: 33px;
}
.sectionLabel {
-fx-text-fill: -fx-pp-dark-text-color;
-fx-font-size: 20px;
}
JFXButton {
-fx-background-color: -fx-pp-light-text-color;
-fx-text-fill: -fx-pp-theme-color;
-fx-font-size: 13px;
}
Label {
-fx-font-size: 15px;
-fx-text-fill: -fx-pp-theme-color;
-fx-effect: -fx-pp-dropshadow-light;
}
JFXToggleButton {
-jfx-toggle-color: -fx-pp-theme-color;
-fx-text-fill: -fx-pp-theme-color;
}
#resetBtn {
-fx-background-color: -fx-pp-theme-color;
-fx-text-fill: -fx-pp-front-color;
-fx-effect: -fx-pp-dropshadow-light;
-fx-font-size: 18;
}
#resetBtn:hover {
-fx-font-size: 20;
}
.jfx-snackbar-content {
-fx-background-color: #323232;
}
.jfx-snackbar-toast {
-fx-text-fill: WHITE;
}
.jfx-snackbar-action {
-fx-text-fill: #ff4081;
}
+33
View File
@@ -0,0 +1,33 @@
#headerLabel {
-fx-font-size: 20px;
-fx-text-fill: -fx-pp-dark-text-color;
}
#closeLabel {
-fx-font-size: 22px;
-fx-text-fill: -fx-pp-dark-text-color;
}
#closeLabel:hover {
-fx-font-size: 24px;
-fx-text-fill: red;
}
#contentLabel {
-fx-font-size: 22px;
-fx-text-fill: -fx-pp-dark-text-color;
}
#optionButton {
-fx-background-color: -fx-pp-theme-color;
-fx-text-fill: -fx-pp-light-text-color;
-fx-font-size: 18px;
-fx-effect: -fx-pp-dropshadow-light;
-fx-max-height: 55;
-fx-focus-traversable: false;
}
#optionButton:hover {
-fx-font-size: 20px !important;
-fx-background-color: -fx-pp-light-theme-color;
}
@@ -0,0 +1,4 @@
/* a separate file to dynamically change snackbar's color */
.jfx-snackbar-toast {
-fx-text-fill: red !important;
}
Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.3 KiB

After

Width:  |  Height:  |  Size: 49 KiB

+64
View File
@@ -0,0 +1,64 @@
<?xml version="1.0" encoding="utf-8"?>
<RaceDefinition>
<CourseName> El Classico </CourseName>
<CentralLat> 57.6679590 </CentralLat>
<CentralLng> 11.8503233 </CentralLng>
<MaxPlayers> 10 </MaxPlayers>
<Marks>
<CompoundMark CompoundMarkID="1">
<Mark Lat="57.670603" Lng="11.828262"/>
<Mark Lat="57.669445" Lng="11.826413"/>
</CompoundMark>
<CompoundMark CompoundMarkID="2">
<Mark Lat="57.6675700" Lng="11.8359880"/>
</CompoundMark>
<CompoundMark CompoundMarkID="3">
<Mark Lat="57.6708220" Lng="11.8433900"/>
<Mark Lat="57.671629" Lng="11.840951"/>
</CompoundMark>
<CompoundMark CompoundMarkID="4">
<Mark Lat="57.664190" Lng="11.829576"/>
<Mark Lat="57.665316" Lng="11.827184"/>
</CompoundMark>
<CompoundMark CompoundMarkID="5">
<Mark Lat="57.672350" Lng="11.842535"/>
<Mark Lat="57.6715240" Lng="11.8444950"/>
</CompoundMark>
</Marks>
<Course>
<OpeningSegment>
<Corner CompoundMarkID="1" Rounding="PS"/>
<Corner CompoundMarkID="2" Rounding="P"/>
</OpeningSegment>
<RepeatingSegment>
<Corner CompoundMarkID="3" Rounding="SP"/>
<Corner CompoundMarkID="4" Rounding="PS"/>
</RepeatingSegment>
<ClosingSegment>
<Corner CompoundMarkID="5" Rounding="PS"/>
</ClosingSegment>
</Course>
<CourseLimit>
<Limit Lat="57.6739450" Lng="11.8417100" />
<Limit Lat="57.6709520" Lng="11.8485010" />
<Limit Lat="57.6690260" Lng="11.8472790" />
<Limit Lat="57.6693140" Lng="11.8457610" />
<Limit Lat="57.6665370" Lng="11.8432910" />
<Limit Lat="57.6641400" Lng="11.8385840" />
<Limit Lat="57.6629430" Lng="11.8332030" />
<Limit Lat="57.6629480" Lng="11.8249660" />
<Limit Lat="57.6686890" Lng="11.8250920" />
<Limit Lat="57.6692230" Lng="11.8231430" />
<Limit Lat="57.6725370" Lng="11.8272480" />
<Limit Lat="57.6708220" Lng="11.8321340" />
</CourseLimit>
</RaceDefinition>
+79
View File
@@ -0,0 +1,79 @@
<?xml version="1.0" encoding="utf-8"?>
<RaceDefinition>
<CourseName> HorseShoe </CourseName>
<CentralLat> -14.6457 </CentralLat>
<CentralLng> 47.612855 </CentralLng>
<MaxPlayers> 5 </MaxPlayers>
<Marks>
<CompoundMark CompoundMarkID="1">
<Mark Lat="-14.071412" Lng="47.05756"/>
<Mark Lat="-14.069914" Lng="47.058541"/>
</CompoundMark>
<CompoundMark CompoundMarkID="2">
<Mark Lat="-14.067194" Lng="47.053818" />
</CompoundMark>
<CompoundMark CompoundMarkID="3">
<Mark Lat="-14.061248" Lng="47.058556" />
</CompoundMark>
<CompoundMark CompoundMarkID="4">
<Mark Lat="-14.060584" Lng="47.063088"/>
<Mark Lat="-14.060617" Lng="47.06374" />
</CompoundMark>
<CompoundMark CompoundMarkID="5">
<Mark Lat="-14.064637" Lng="47.060307"/>
<Mark Lat="-14.06477" Lng="47.061165"/>
</CompoundMark>
<CompoundMark CompoundMarkID="6">
<Mark Lat="-14.063839" Lng="47.06762"/>
</CompoundMark>
<CompoundMark CompoundMarkID="7">
<Mark Lat="-14.068954" Lng="47.066349"/>
</CompoundMark>
<CompoundMark CompoundMarkID="8">
<Mark Lat="-14.07025" Lng="47.060238"/>
<Mark Lat="-14.071047" Lng="47.060307"/>
</CompoundMark>
</Marks>
<Course>
<OpeningSegment>
<Corner CompoundMarkID="1" Rounding="PS"/>
<Corner CompoundMarkID="2" Rounding="S"/>
<Corner CompoundMarkID="3" Rounding="S"/>
<Corner CompoundMarkID="4" Rounding="SP"/>
</OpeningSegment>
<RepeatingSegment>
<Corner CompoundMarkID="5" Rounding="SP"/>
<Corner CompoundMarkID="4" Rounding="PS"/>
</RepeatingSegment>
<ClosingSegment>
<Corner CompoundMarkID="6" Rounding="S"/>
<Corner CompoundMarkID="7" Rounding="S"/>
<Corner CompoundMarkID="8" Rounding="SP" />
</ClosingSegment>
</Course>
<CourseLimit>
<Limit Lat="-14.073371" Lng="47.058213" />
<Limit Lat="-14.06453" Lng="47.050003" />
<Limit Lat="-14.059022" Lng="47.057286" />
<Limit Lat="-14.058723" Lng="47.064358" />
<Limit Lat="-14.06261" Lng="47.071293" />
<Limit Lat="-14.070814" Lng="47.06762" />
<Limit Lat="-14.072773" Lng="47.059689" />
<Limit Lat="-14.068323" Lng="47.059449" />
<Limit Lat="-14.064969" Lng="47.065113" />
<Limit Lat="-14.066131" Lng="47.05986" />
<Limit Lat="-14.063441" Lng="47.058419" />
<Limit Lat="-14.068091" Lng="47.059174" />
</CourseLimit>
</RaceDefinition>
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+100
View File
@@ -0,0 +1,100 @@
<?xml version="1.0" encoding="utf-8"?>
<COLLADA xmlns="http://www.collada.org/2005/11/COLLADASchema" version="1.4.1">
<asset>
<contributor>
<author>Blender User</author>
<authoring_tool>Blender 2.78.0 commit date:2016-09-26, commit time:12:42, hash:4bb1e22</authoring_tool>
</contributor>
<created>2017-09-26T01:05:25</created>
<modified>2017-09-26T01:05:25</modified>
<unit name="meter" meter="1"/>
<up_axis>Z_UP</up_axis>
</asset>
<library_images/>
<library_effects>
<effect id="Material_004-effect">
<profile_COMMON>
<technique sid="common">
<phong>
<emission>
<color sid="emission">0 0 0 1</color>
</emission>
<ambient>
<color sid="ambient">0 0 0 1</color>
</ambient>
<diffuse>
<color sid="diffuse">0.01173657 0.01136953 0.01164411 1</color>
</diffuse>
<specular>
<color sid="specular">0 0 0 1</color>
</specular>
<shininess>
<float sid="shininess">50</float>
</shininess>
<index_of_refraction>
<float sid="index_of_refraction">1</float>
</index_of_refraction>
</phong>
</technique>
</profile_COMMON>
</effect>
</library_effects>
<library_materials>
<material id="Material_004-material" name="Material_004">
<instance_effect url="#Material_004-effect"/>
</material>
</library_materials>
<library_geometries>
<geometry id="Plane_004-mesh" name="Plane.004">
<mesh>
<source id="Plane_004-mesh-positions">
<float_array id="Plane_004-mesh-positions-array" count="240">-1 -0.09999954 0.01554518 1 -0.09999954 0.01554518 -1 0.1000005 0.01554518 1 0.1000005 0.01554518 1 0.1000005 0.01554518 1 -0.09999954 0.01554518 1.019509 -0.09807801 0.01554518 1.038269 -0.09238743 0.01554518 1.055557 -0.08314645 0.01554518 1.070711 -0.07071018 0.01554518 1.083147 -0.05555647 0.01554518 1.092388 -0.03826785 0.01554518 1.098079 -0.01950848 0.01554518 1.1 5.96046e-7 0.01554518 1.098079 0.01950955 0.01554518 1.092388 0.03826892 0.01554518 1.083147 0.05555754 0.01554518 1.070711 0.07071125 0.01554518 1.055557 0.08314752 0.01554518 1.038269 0.09238851 0.01554518 1.019509 0.09807896 0.01554518 -1 0.1000005 0.01554518 -1.019509 0.0980789 0.01554518 -1.038268 0.09238833 0.01554518 -1.055557 0.08314734 0.01554518 -1.070711 0.07071107 0.01554518 -1.083147 0.05555737 0.01554518 -1.092388 0.03826874 0.01554518 -1.098078 0.01950937 0.01554518 -1.1 4.56348e-7 0.01554518 -1.098078 -0.0195086 0.01554518 -1.092388 -0.03826785 0.01554518 -1.083147 -0.05555647 0.01554518 -1.070711 -0.07071018 0.01554518 -1.055557 -0.08314645 0.01554518 -1.038268 -0.09238755 0.01554518 -1.019509 -0.09807813 0.01554518 -0.9999997 -0.09999954 0.01554518 1 4.76837e-7 0.01554518 -1 4.76837e-7 0.01554518 -1 4.76837e-7 0.04452198 -1 0.1000005 0.04452198 -1 -0.09999954 0.04452198 1 -0.09999954 0.04452198 1 0.1000005 0.04452198 1.019509 -0.09807801 0.04452198 1 -0.09999954 0.04452198 1.038269 -0.09238743 0.04452198 1.055557 -0.08314645 0.04452198 1.070711 -0.07071018 0.04452198 1.083147 -0.05555647 0.04452198 1.092388 -0.03826785 0.04452198 1.098079 -0.01950848 0.04452198 1.1 5.96046e-7 0.04452198 1.098079 0.01950955 0.04452198 1.092388 0.03826892 0.04452198 1.083147 0.05555754 0.04452198 1.070711 0.07071125 0.04452198 1.055557 0.08314752 0.04452198 1.038269 0.09238851 0.04452198 1.019509 0.09807896 0.04452198 1 0.1000005 0.04452198 -1.019509 0.0980789 0.04452198 -1 0.1000005 0.04452198 -1.038268 0.09238833 0.04452198 -1.055557 0.08314734 0.04452198 -1.070711 0.07071107 0.04452198 -1.083147 0.05555737 0.04452198 -1.092388 0.03826874 0.04452198 -1.098078 0.01950937 0.04452198 -1.1 4.56348e-7 0.04452198 -1.098078 -0.0195086 0.04452198 -1.092388 -0.03826785 0.04452198 -1.083147 -0.05555647 0.04452198 -1.070711 -0.07071018 0.04452198 -1.055557 -0.08314645 0.04452198 -1.038268 -0.09238755 0.04452198 -1.019509 -0.09807813 0.04452198 -0.9999997 -0.09999954 0.04452198 1 4.76837e-7 0.04452198</float_array>
<technique_common>
<accessor source="#Plane_004-mesh-positions-array" count="80" stride="3">
<param name="X" type="float"/>
<param name="Y" type="float"/>
<param name="Z" type="float"/>
</accessor>
</technique_common>
</source>
<source id="Plane_004-mesh-normals">
<float_array id="Plane_004-mesh-normals-array" count="270">0 0 -1 1.19345e-7 0 -1 0 0 -1 0 0 -1 -1.19344e-7 0 -1 -1.19343e-7 0 -1 0 0 -1 0 0 -1 -1.19343e-7 0 -1 0 0 1 1.19345e-7 0 1 0 0 1 -4.77394e-7 0 1 -1.19344e-7 0 1 -4.77375e-7 0 1 4.77387e-7 0 1 0 0 1 -1.19345e-7 0 1 2.38692e-7 0 1 2.38687e-7 0 1 -2.38687e-7 0 1 -0.6343871 0.7730156 0 -1 0 0 0.9569398 -0.2902864 0 1 0 0 -0.7730116 0.6343919 0 0.9951861 -0.09800404 0 -0.8819217 0.4713962 0 0.9951872 0.09799164 0 -0.9569509 0.2902504 0 0.9569326 0.2903105 0 -0.9951821 0.09804385 0 0.8819217 0.471396 0 0.923877 0.3826896 0 -0.9951823 -0.09804314 0 0.773018 0.6343841 0 0.3826818 -0.9238802 0 0 -1 0 -0.9569467 -0.2902641 0 0.6343807 0.7730209 0 0 1 0 -0.8819217 -0.4713962 0 0.4714139 0.8819121 0 0.09802061 -0.9951844 0 -0.7730181 -0.6343839 0 1 3.85683e-6 0 0.2902668 0.9569458 0 -0.2902731 0.9569439 0 -0.6343807 -0.7730209 0 0.09802073 0.9951844 0 -0.4714134 0.8819125 0 -0.4713908 -0.8819245 0 -0.09802436 0.995184 0 0.6343871 -0.7730156 0 -0.2902895 -0.956939 0 -0.2902888 0.9569392 0 0.7730117 -0.6343918 0 -0.09801268 -0.9951853 0 -0.4713921 0.8819237 0 0.8819217 -0.4713962 0 -0.6343807 0.7730209 0 0.9569326 -0.2903105 0 -0.7730181 0.634384 0 0.9951873 -0.0979911 0 0.995186 0.09800463 0 -0.9569472 0.2902624 0 0.9569398 0.2902864 0 -0.9951821 0.09804385 0 0.923877 0.3826897 0 0.7730115 0.634392 0 0.3826841 -0.9238793 0 -0.9569503 -0.2902522 0 0.6343871 0.7730156 0 0.4714112 0.8819137 0 0.09801995 -0.9951846 0 -0.7730117 -0.6343919 0 1 2.57122e-6 0 0.2902686 0.9569453 0 -0.2902749 0.9569434 0 -0.6343871 -0.7730156 0 0.09802132 0.9951844 0 -0.4714105 0.8819139 0 -0.4713962 -0.8819217 0 -0.09802371 0.9951841 0 0.6343807 -0.7730209 0 -0.2902895 -0.956939 0 -0.2902888 0.9569392 0 0.7730181 -0.634384 0 -0.09801262 -0.9951853 0 -0.4713866 0.8819267 0</float_array>
<technique_common>
<accessor source="#Plane_004-mesh-normals-array" count="90" stride="3">
<param name="X" type="float"/>
<param name="Y" type="float"/>
<param name="Z" type="float"/>
</accessor>
</technique_common>
</source>
<vertices id="Plane_004-mesh-vertices">
<input semantic="POSITION" source="#Plane_004-mesh-positions"/>
</vertices>
<polylist material="Material_004-material" count="158">
<input semantic="VERTEX" source="#Plane_004-mesh-vertices" offset="0"/>
<input semantic="NORMAL" source="#Plane_004-mesh-normals" offset="1"/>
<vcount>3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 </vcount>
<p>38 0 0 0 39 0 6 1 5 1 38 1 8 0 6 0 38 0 9 2 38 2 10 2 8 0 38 0 9 0 10 0 38 0 11 0 11 0 38 0 12 0 12 3 38 3 13 3 4 0 20 0 38 0 19 0 38 0 20 0 18 0 38 0 19 0 17 0 38 0 18 0 16 0 38 0 17 0 15 0 38 0 16 0 14 0 38 0 15 0 13 0 38 0 14 0 37 4 36 4 39 4 35 0 39 0 36 0 21 0 39 0 22 0 34 0 39 0 35 0 33 5 39 5 34 5 32 0 39 0 33 0 31 0 39 0 32 0 30 0 39 0 31 0 29 0 39 0 30 0 28 6 39 6 29 6 27 0 39 0 28 0 26 0 39 0 27 0 25 7 39 7 26 7 22 0 39 0 23 0 23 0 39 0 24 0 24 8 39 8 25 8 40 9 43 9 79 9 45 9 79 9 46 9 48 9 79 9 45 9 49 9 50 9 79 9 48 9 49 9 79 9 50 10 51 10 79 10 51 9 52 9 79 9 52 11 53 11 79 11 61 9 79 9 60 9 59 9 60 9 79 9 58 12 59 12 79 12 57 9 58 9 79 9 56 9 57 9 79 9 55 9 56 9 79 9 54 13 55 13 79 13 53 9 54 9 79 9 78 14 40 14 77 14 76 15 77 15 40 15 63 9 62 9 40 9 75 9 76 9 40 9 74 9 75 9 40 9 73 9 74 9 40 9 72 9 73 9 40 9 71 9 72 9 40 9 70 9 71 9 40 9 69 16 70 16 40 16 68 9 69 9 40 9 67 17 68 17 40 17 66 18 67 18 40 18 62 9 64 9 40 9 64 19 65 19 40 19 65 20 66 20 40 20 25 21 65 21 24 21 38 22 61 22 4 22 12 23 51 23 11 23 38 24 43 24 1 24 26 25 66 25 25 25 13 26 52 26 12 26 0 22 40 22 39 22 27 27 67 27 26 27 14 28 53 28 13 28 5 22 79 22 38 22 28 29 68 29 27 29 15 30 54 30 14 30 29 31 69 31 28 31 16 32 55 32 15 32 38 33 47 33 7 33 39 22 41 22 2 22 30 34 70 34 29 34 17 35 56 35 16 35 8 36 45 36 6 36 1 37 42 37 0 37 31 38 71 38 30 38 18 39 57 39 17 39 2 40 44 40 3 40 32 41 72 41 31 41 19 42 58 42 18 42 6 43 46 43 5 43 33 44 73 44 32 44 37 45 40 45 78 45 20 46 59 46 19 46 6 47 47 47 7 47 34 48 74 48 33 48 4 49 60 49 20 49 7 50 48 50 8 50 35 51 75 51 34 51 22 52 63 52 21 52 9 53 48 53 8 53 36 54 76 54 35 54 21 24 40 24 39 24 23 55 62 55 22 55 10 56 49 56 9 56 37 57 77 57 36 57 24 58 64 58 23 58 11 59 50 59 10 59 3 24 79 24 38 24 39 0 2 0 3 0 38 0 1 0 0 0 39 0 3 0 38 0 44 9 41 9 40 9 40 9 42 9 43 9 79 9 44 9 40 9 25 60 66 60 65 60 38 22 79 22 61 22 12 61 52 61 51 61 38 24 79 24 43 24 26 62 67 62 66 62 13 63 53 63 52 63 0 22 42 22 40 22 27 27 68 27 67 27 14 64 54 64 53 64 5 22 46 22 79 22 28 65 69 65 68 65 15 66 55 66 54 66 29 67 70 67 69 67 16 32 56 32 55 32 38 68 79 68 47 68 39 22 40 22 41 22 30 34 71 34 70 34 17 69 57 69 56 69 8 70 48 70 45 70 1 37 43 37 42 37 31 71 72 71 71 71 18 72 58 72 57 72 2 40 41 40 44 40 32 41 73 41 72 41 19 73 59 73 58 73 6 74 45 74 46 74 33 75 74 75 73 75 37 76 39 76 40 76 20 77 60 77 59 77 6 78 45 78 47 78 34 79 75 79 74 79 4 80 61 80 60 80 7 81 47 81 48 81 35 82 76 82 75 82 22 83 62 83 63 83 9 84 49 84 48 84 36 85 77 85 76 85 21 24 63 24 40 24 23 86 64 86 62 86 10 87 50 87 49 87 37 88 78 88 77 88 24 89 65 89 64 89 11 59 51 59 50 59 3 24 44 24 79 24</p>
</polylist>
</mesh>
</geometry>
</library_geometries>
<library_controllers/>
<library_visual_scenes>
<visual_scene id="Scene" name="Scene">
<node id="Plane" name="Plane" type="NODE">
<matrix sid="transform">0.4856636 0 0 0 0 0.6802911 0 -4.76837e-7 0 0 1 0 0 0 0 1</matrix>
<instance_geometry url="#Plane_004-mesh" name="Plane">
<bind_material>
<technique_common>
<instance_material symbol="Material_004-material" target="#Material_004-material"/>
</technique_common>
</bind_material>
</instance_geometry>
</node>
</visual_scene>
</library_visual_scenes>
<scene>
<instance_visual_scene url="#Scene"/>
</scene>
</COLLADA>
File diff suppressed because one or more lines are too long
Binary file not shown.

Before

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 52 KiB

Some files were not shown because too many files have changed in this diff Show More