1414 * the License.
1515 */
1616
17- package com .esri .samples .scene .animate_3d_symbols ;
17+ package com .esri .samples .scene .animate_3d_graphic ;
1818
1919import java .io .BufferedReader ;
2020import java .io .File ;
2121import java .io .IOException ;
2222import java .io .InputStreamReader ;
23- import java .net .URISyntaxException ;
2423import java .util .ArrayList ;
2524import java .util .HashMap ;
2625import java .util .List ;
3635import javafx .animation .Timeline ;
3736import javafx .application .Platform ;
3837import javafx .beans .binding .Bindings ;
39- import javafx .event .Event ;
40- import javafx .event .EventHandler ;
4138import javafx .fxml .FXML ;
4239import javafx .scene .control .ComboBox ;
40+ import javafx .scene .control .Label ;
4341import javafx .scene .control .ToggleButton ;
44- import javafx .scene .input .MouseEvent ;
45- import javafx .scene .input .ScrollEvent ;
4642import javafx .util .Duration ;
4743
48- /**
49- * Controller class. Automatically instantiated when the FXML loads due to the fx:controller attribute.
50- */
51- public class Animate3dSymbolsController {
44+ public class Animate3dGraphicController {
45+
5246 // injected elements from fxml
53- @ FXML private CameraModel cameraModel ;
5447 @ FXML private AnimationModel animationModel ;
55- @ FXML private PlaneModel planeModel ;
5648 @ FXML private SceneView sceneView ;
5749 @ FXML private MapView mapView ;
5850 @ FXML private ComboBox <String > missionSelector ;
5951 @ FXML private ToggleButton playButton ;
6052 @ FXML private ToggleButton followButton ;
6153 @ FXML private Timeline animation ;
54+ @ FXML private Label altitudeLabel ;
55+ @ FXML private Label headingLabel ;
56+ @ FXML private Label pitchLabel ;
57+ @ FXML private Label rollLabel ;
6258
63- private Camera camera ;
59+ private OrbitGeoElementCameraController orbitCameraController ;
60+ private List <Map <String , Object >> missionData ;
6461 private Graphic plane3D ;
6562 private Graphic plane2D ;
66- private List <Map <String , Object >> missionData ;
6763 private Graphic routeGraphic ;
6864
69- private static final String POSITION = "POSITION" ;
70- private static final String HEADING = "HEADING" ;
71- private static final String PITCH = "PITCH" ;
72- private static final String ROLL = "ROLL" ;
7365 private static final SpatialReference WGS84 = SpatialReferences .getWgs84 ();
7466 private static final String ELEVATION_IMAGE_SERVICE =
7567 "http://elevation3d.arcgis.com/arcgis/rest/services/WorldElevation3D/Terrain3D/ImageServer" ;
@@ -84,10 +76,6 @@ public void initialize() {
8476 ArcGISScene scene = new ArcGISScene (Basemap .createImagery ());
8577 sceneView .setArcGISScene (scene );
8678
87- // set initial camera viewpoint
88- camera = new Camera (-111.8568649 , 36.05793612 , 2000 , 10.0 , 80.0 , 300.0 );
89- sceneView .setViewpointCamera (camera );
90-
9179 // add elevation data
9280 Surface surface = new Surface ();
9381 surface .getElevationSources ().add (new ArcGISTiledElevationSource (ELEVATION_IMAGE_SERVICE ));
@@ -98,9 +86,8 @@ public void initialize() {
9886 sceneOverlay .getSceneProperties ().setSurfacePlacement (LayerSceneProperties .SurfacePlacement .ABSOLUTE );
9987 sceneView .getGraphicsOverlays ().add (sceneOverlay );
10088
101- // create renderer to handle updating plane rotation using the GPU
89+ // create renderer to handle updating plane's orientation
10290 SimpleRenderer renderer3D = new SimpleRenderer ();
103- renderer3D .setRotationType (RotationType .GEOGRAPHIC );
10491 Renderer .SceneProperties renderProperties = renderer3D .getSceneProperties ();
10592 renderProperties .setHeadingExpression ("[HEADING]" );
10693 renderProperties .setPitchExpression ("[PITCH]" );
@@ -115,42 +102,46 @@ public void initialize() {
115102 GraphicsOverlay mapOverlay = new GraphicsOverlay ();
116103 mapView .getGraphicsOverlays ().add (mapOverlay );
117104
118- // create renderer to handle updating plane heading using the graphics card
105+ // create renderer to rotate the plane graphic in the mini map
119106 SimpleRenderer renderer2D = new SimpleRenderer ();
107+ SimpleMarkerSymbol plane2DSymbol = new SimpleMarkerSymbol (SimpleMarkerSymbol .Style .TRIANGLE , 0xFF0000FF , 10 );
108+ renderer2D .setSymbol (plane2DSymbol );
120109 renderer2D .setRotationExpression ("[ANGLE]" );
121110 mapOverlay .setRenderer (renderer2D );
122111
123- // set up route graphic
112+ // create a placeholder graphic for showing the mission route in mini map
124113 SimpleLineSymbol routeSymbol = new SimpleLineSymbol (SimpleLineSymbol .Style .SOLID , 0xFFFF0000 , 2 );
125114 routeGraphic = new Graphic ();
126115 routeGraphic .setSymbol (routeSymbol );
127116 mapOverlay .getGraphics ().add (routeGraphic );
128117
129- // create 2D and 3D plane graphics
130- plane2D = create2DPlane ();
118+ // create a graphic with a blue (0xFF0000FF) triangle symbol to represent the plane on the mini map
119+ Map <String , Object > attributes = new HashMap <>();
120+ attributes .put ("ANGLE" , 0f );
121+ plane2D = new Graphic (new Point (0 , 0 , WGS84 ), attributes );
131122 mapOverlay .getGraphics ().add (plane2D );
132- plane3D = create3DPlane ();
123+
124+ // create a graphic with a ModelSceneSymbol of a plane to add to the scene
125+ String modelURI = new File ("./samples-data/bristol/Collada/Bristol.dae" ).getAbsolutePath ();
126+ ModelSceneSymbol plane3DSymbol = new ModelSceneSymbol (modelURI , 1.0 );
127+ plane3DSymbol .loadAsync ();
128+ plane3D = new Graphic (new Point (0 , 0 , 0 , WGS84 ), plane3DSymbol );
133129 sceneOverlay .getGraphics ().add (plane3D );
134130
131+ // create an orbit camera controller to follow the plane
132+ orbitCameraController = new OrbitGeoElementCameraController (plane3D , 20.0 );
133+ orbitCameraController .setCameraPitchOffset (75.0 );
134+ sceneView .setCameraController (orbitCameraController );
135+
135136 // setup animation to render a new frame every 20 ms by default
136137 animation .getKeyFrames ().add (new KeyFrame (Duration .millis (20 ), e -> animate (animationModel .nextKeyframe ())));
137138
138139 // bind button properties
139- followButton .disableProperty ().bind (playButton .selectedProperty ().not ());
140140 followButton .textProperty ().bind (Bindings .createStringBinding (() -> followButton .isSelected () ?
141141 "Free cam" : "Follow" , followButton .selectedProperty ()));
142142 playButton .textProperty ().bind (Bindings .createStringBinding (() -> playButton .isSelected () ?
143143 "Stop" : "Play" , playButton .selectedProperty ()));
144144
145- // disable scroll zoom and dragging in follow mode
146- EventHandler <Event > handler = (e ) -> {
147- if (!followButton .isDisabled () && cameraModel .isFollowing ()) {
148- e .consume ();
149- }
150- };
151- sceneView .addEventFilter (ScrollEvent .ANY , handler );
152- sceneView .addEventFilter (MouseEvent .ANY , handler );
153-
154145 // open default mission selection
155146 changeMission ();
156147
@@ -161,36 +152,7 @@ public void initialize() {
161152 }
162153
163154 /**
164- * Creates a 3D graphic representing the plane in the scene.
165- *
166- * @throws URISyntaxException if model cannot be loaded
167- */
168- private Graphic create3DPlane () throws URISyntaxException {
169-
170- // load the plane's 3D model symbol
171- String modelURI = new File ("./samples-data/bristol/Collada/Bristol.dae" ).getAbsolutePath ();
172- ModelSceneSymbol plane3DSymbol = new ModelSceneSymbol (modelURI , 1.0 );
173- plane3DSymbol .loadAsync ();
174-
175- // create the graphic
176- return new Graphic (new Point (0 , 0 , 0 , WGS84 ), plane3DSymbol );
177- }
178-
179- /**
180- * Creates a 2D graphic representing the plane on the mini map. Adds the graphic to the map view's graphics overlay.
181- */
182- private Graphic create2DPlane () {
183- // create a blue (0xFF0000FF) triangle symbol to represent the plane on the mini map
184- SimpleMarkerSymbol plane2DSymbol = new SimpleMarkerSymbol (SimpleMarkerSymbol .Style .TRIANGLE , 0xFF0000FF , 10 );
185-
186- // create a graphic with the symbol and attributes
187- Map <String , Object > attributes = new HashMap <>();
188- attributes .put ("[ANGLE]" , 0f );
189- return new Graphic (new Point (0 , 0 , WGS84 ), attributes , plane2DSymbol );
190- }
191-
192- /**
193- * Called when a new mission is selected from the dropdown.
155+ * Change the mission data and reset the animation.
194156 */
195157 @ FXML
196158 private void changeMission () {
@@ -206,17 +168,12 @@ private void changeMission() {
206168
207169 // draw mission route on mini map
208170 PointCollection points = new PointCollection (WGS84 );
209- points .addAll (missionData .stream ().map (m -> (Point ) m .get (POSITION )).collect (Collectors .toList ()));
171+ points .addAll (missionData .stream ().map (m -> (Point ) m .get (" POSITION" )).collect (Collectors .toList ()));
210172 Polyline route = new Polyline (points );
211173 routeGraphic .setGeometry (route );
212174
213175 // refresh mini map zoom and show initial keyframe
214176 mapView .setViewpointScaleAsync (100000 ).addDoneListener (() -> Platform .runLater (() -> animate (0 )));
215- animation .stop ();
216-
217- // enable play button
218- playButton .setSelected (false );
219- playButton .setDisable (false );
220177 }
221178
222179 /**
@@ -231,22 +188,20 @@ private List<Map<String, Object>> getMissionData(String mission) {
231188 // open a file reader to the mission file that automatically closes after read
232189 try (BufferedReader missionFile = new BufferedReader (new InputStreamReader (getClass ().getResourceAsStream
233190 ("/csv/" + mission )))) {
234- List <Map <String , Object >> missionData = new ArrayList <>();
235- missionFile .lines ()
191+ return missionFile .lines ()
236192 //ex: -156.3666517,20.6255059,999.999908,83.77659,1.05E-09,-47.766567
237193 .map (l -> l .split ("," ))
238194 .map (l -> {
239195 // create a map of parameters (ordinates) to values
240196 Map <String , Object > ordinates = new HashMap <>();
241- ordinates .put (POSITION , new Point (Double .valueOf (l [0 ]), Double .valueOf (l [1 ]), Double .valueOf (l [2 ]), WGS84 ));
242- ordinates .put (HEADING , Double .valueOf (l [3 ]));
243- ordinates .put (PITCH , Double .valueOf (l [4 ]));
244- ordinates .put (ROLL , Double .valueOf (l [5 ]));
197+ ordinates .put ("POSITION" , new Point (Float .valueOf (l [0 ]), Float .valueOf (l [1 ]), Float .valueOf (l [2 ]),
198+ WGS84 ));
199+ ordinates .put ("HEADING" , Float .valueOf (l [3 ]));
200+ ordinates .put ("PITCH" , Float .valueOf (l [4 ]));
201+ ordinates .put ("ROLL" , Float .valueOf (l [5 ]));
245202 return ordinates ;
246203 })
247- .collect (Collectors .toCollection (() -> missionData ));
248-
249- return missionData ;
204+ .collect (Collectors .toList ());
250205 } catch (IOException e ) {
251206 e .printStackTrace ();
252207 }
@@ -261,41 +216,34 @@ private List<Map<String, Object>> getMissionData(String mission) {
261216 */
262217 private void animate (int keyframe ) {
263218
264- // get the next POSITION
219+ // get the next position from the mission data
265220 Map <String , Object > datum = missionData .get (keyframe );
266- Point position = (Point ) datum .get (POSITION );
267-
268- // update the model bean with new parameters
269- planeModel .setAltitude (position .getZ ());
270- planeModel .setHeading ((double ) datum .get (HEADING ));
271- planeModel .setPitch ((double ) datum .get (PITCH ));
272- planeModel .setRoll ((double ) datum .get (ROLL ));
221+ Point position = (Point ) datum .get ("POSITION" );
273222
274- // move 2D plane to next POSITION
275- plane2D .setGeometry (position );
223+ // update the position parameters pane
224+ altitudeLabel .setText (String .format ("%.2f" , position .getZ ()));
225+ headingLabel .setText (String .format ("%.2f" , (float ) datum .get ("HEADING" )));
226+ pitchLabel .setText (String .format ("%.2f" , (float ) datum .get ("PITCH" )));
227+ rollLabel .setText (String .format ("%.2f" , (float ) datum .get ("ROLL" )));
276228
277- // move 3D plane to next POSITION
229+ // update plane's position and orientation
278230 plane3D .setGeometry (position );
279- // update attribute expressions to immediately update rotation
280- plane3D .getAttributes ().put (HEADING , planeModel .getHeading ());
281- plane3D .getAttributes ().put (PITCH , planeModel .getPitch ());
282- plane3D .getAttributes ().put (ROLL , planeModel .getRoll ());
283-
284- if (cameraModel .isFollowing ()) {
285- // move the camera to follow the plane
286- camera = new Camera (position , cameraModel .getDistance (), planeModel .getHeading (), cameraModel .getAngle (),
287- planeModel .getRoll ());
288- sceneView .setViewpointCamera (camera );
289-
290- // rotate the map view about the direction of motion
291- mapView .setViewpoint (new Viewpoint (position , mapView .getMapScale (), 360 + planeModel .getHeading ()));
231+ plane3D .getAttributes ().put ("HEADING" , datum .get ("HEADING" ));
232+ plane3D .getAttributes ().put ("PITCH" , datum .get ("PITCH" ));
233+ plane3D .getAttributes ().put ("ROLL" , datum .get ("ROLL" ));
234+
235+ // update mini map plane's position and rotation
236+ plane2D .setGeometry (position );
237+ if (followButton .isSelected ()) {
238+ // rotate the map view in the direction of motion to make graphic always point up
239+ mapView .setViewpoint (new Viewpoint (position , mapView .getMapScale (), 360 + (float ) datum .get ("HEADING" )));
292240 } else {
293- plane2D .getAttributes ().put ("[ ANGLE] " , 360 + planeModel . getHeading ( ) - mapView .getMapRotation ());
241+ plane2D .getAttributes ().put ("ANGLE" , 360 + ( float ) datum . get ( "HEADING" ) - mapView .getMapRotation ());
294242 }
295243 }
296244
297245 /**
298- * Switches the animation on or off depending on the toggled state of the play button .
246+ * Switches the animation on or off.
299247 */
300248 @ FXML
301249 private void togglePlay () {
@@ -308,27 +256,32 @@ private void togglePlay() {
308256 }
309257
310258 /**
311- * Switches the toggle mode on the camera model when the toggle button is clicked .
259+ * Switches between the orbiting camera controller and default globe camera controller .
312260 */
313261 @ FXML
314262 private void toggleFollow () {
315263
316264 if (followButton .isSelected ()) {
317- plane2D .getAttributes ().put ("[ANGLE]" , 0f );
265+ // reset mini-map plane's rotation to point up
266+ plane2D .getAttributes ().put ("ANGLE" , 0f );
267+ // set orbit camera controller
268+ sceneView .setCameraController (orbitCameraController );
269+ } else {
270+ // set camera controller back to default
271+ sceneView .setCameraController (new GlobeCameraController ());
318272 }
319- cameraModel .setFollowing (followButton .isSelected ());
320273 }
321274
322275 /**
323- * Sets the map view scale to zoom in one exponential step .
276+ * Zoom in mini- map scale.
324277 */
325278 @ FXML
326279 private void zoomInMap () {
327280 mapView .setViewpoint (new Viewpoint ((Point ) plane2D .getGeometry (), mapView .getMapScale () / 5 ));
328281 }
329282
330283 /**
331- * Sets the map view scale to zoom out one exponential step .
284+ * Zoom out mini- map scale.
332285 */
333286 @ FXML
334287 private void zoomOutMap () {
0 commit comments