Skip to content

Commit 8f7d721

Browse files
authored
Merge pull request #145 from Esri/tyle8552/follow-graphic-camera-controller
Tyle8552/follow graphic camera controller
2 parents 8bdd51b + 7fe35f2 commit 8f7d721

File tree

9 files changed

+101
-451
lines changed

9 files changed

+101
-451
lines changed

build.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
apply plugin: 'com.esri.arcgisruntime.java'
21
apply plugin: 'application'
32
apply plugin: 'idea'
43
apply plugin: 'eclipse'
4+
apply plugin: 'com.esri.arcgisruntime.java'
55

66
buildscript {
77
repositories {

src/main/java/com/esri/samples/scene/animate_3d_symbols/Animate3dSymbolsController.java renamed to src/main/java/com/esri/samples/scene/animate_3d_graphic/Animate3dGraphicController.java

Lines changed: 68 additions & 115 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,12 @@
1414
* the License.
1515
*/
1616

17-
package com.esri.samples.scene.animate_3d_symbols;
17+
package com.esri.samples.scene.animate_3d_graphic;
1818

1919
import java.io.BufferedReader;
2020
import java.io.File;
2121
import java.io.IOException;
2222
import java.io.InputStreamReader;
23-
import java.net.URISyntaxException;
2423
import java.util.ArrayList;
2524
import java.util.HashMap;
2625
import java.util.List;
@@ -36,40 +35,33 @@
3635
import javafx.animation.Timeline;
3736
import javafx.application.Platform;
3837
import javafx.beans.binding.Bindings;
39-
import javafx.event.Event;
40-
import javafx.event.EventHandler;
4138
import javafx.fxml.FXML;
4239
import javafx.scene.control.ComboBox;
40+
import javafx.scene.control.Label;
4341
import javafx.scene.control.ToggleButton;
44-
import javafx.scene.input.MouseEvent;
45-
import javafx.scene.input.ScrollEvent;
4642
import 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() {

src/main/java/com/esri/samples/scene/animate_3d_symbols/Animate3dSymbolsSample.java renamed to src/main/java/com/esri/samples/scene/animate_3d_graphic/Animate3dGraphicSample.java

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
* the License.
1515
*/
1616

17-
package com.esri.samples.scene.animate_3d_symbols;
17+
package com.esri.samples.scene.animate_3d_graphic;
1818

1919
import java.io.IOException;
2020

@@ -24,20 +24,20 @@
2424
import javafx.scene.Scene;
2525
import javafx.stage.Stage;
2626

27-
public class Animate3dSymbolsSample extends Application {
27+
public class Animate3dGraphicSample extends Application {
2828

29-
private static Animate3dSymbolsController controller;
29+
private static Animate3dGraphicController controller;
3030

3131
@Override
3232
public void start(Stage stage) throws IOException {
3333
// set up the scene
34-
FXMLLoader loader = new FXMLLoader(getClass().getResource("/fxml/animate_3d_symbols.fxml"));
34+
FXMLLoader loader = new FXMLLoader(getClass().getResource("/fxml/animate_3d_graphic.fxml"));
3535
Parent root = loader.load();
3636
controller = loader.getController();
3737
Scene scene = new Scene(root);
3838

3939
// set up the stage
40-
stage.setTitle("Animate 3d Symbols Sample");
40+
stage.setTitle("Animate 3d Graphic Sample");
4141
stage.setWidth(800);
4242
stage.setHeight(700);
4343
stage.setScene(scene);

src/main/java/com/esri/samples/scene/animate_3d_symbols/AnimationModel.java renamed to src/main/java/com/esri/samples/scene/animate_3d_graphic/AnimationModel.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
* the License.
1515
*/
1616

17-
package com.esri.samples.scene.animate_3d_symbols;
17+
package com.esri.samples.scene.animate_3d_graphic;
1818

1919
import javafx.beans.property.IntegerProperty;
2020
import javafx.beans.property.SimpleIntegerProperty;
@@ -86,7 +86,7 @@ public int getKeyframe() {
8686
*
8787
* @return next keyframe
8888
*/
89-
public int nextKeyframe() {
89+
int nextKeyframe() {
9090
setKeyframe(getKeyframe() + 1);
9191
return getKeyframe();
9292
}

0 commit comments

Comments
 (0)