Animate 3D symbols

View inQMLC++
View on GitHub
Sample viewer app

Demonstrates how to animate a graphic's position and rotation and follow it using a OrbitGeoElementCameraController. Also shows how to combine a SceneView and MapView in an MVC application with property binding.

screenshot

Use case

Visualize movement through a 3D landscape.

How to use the sample

Animation Controls (Top Left Corner):

  • Select a mission -- selects a location with a route for the plane to fly
  • Play/Pause -- toggles playing and stopping the animation
  • Fixed/Follow -- toggles the camera's free cam mode and follow
  • Mission progress -- shows how far along the route the plane is. Slide to change keyframe in animation

Camera Controls (Top Right Corner):

  • Camare zoom -- distance between camera and plane
  • Camera angle -- viewing angle between camera and plane
  • Flight speed -- controls speed of animation

2D Map Controls (Bottom Left Corner):

  • Plus and Minus -- controls distance of 2D view from ground level

How it works

  1. Create a GraphicsOverlay and add it to the SceneView.
  2. Create a ModelSceneSymbol object.
  3. Create a Graphic object and set its geometry to a Point.
  4. Set the ModelSceneSymbol object to the graphic.
  5. Add heading, pitch, and roll attributes to the graphic. Get the attributes from the graphic with Graphic::attributes.
  6. Create a SimpleRenderer object and set its expression properties.
  7. Add graphic and a renderer to the graphics overlay.
  8. Create a OrbitGeoElementCameraController which is set to target the graphic.
  9. Assign the camera controller to the SceneView.
  10. Update the graphic's location, heading, pitch, and roll.

Relevant API

  • Camera
  • GlobeCameraController
  • Graphic
  • GraphicsOverlay
  • ModelSceneSymbol
  • OrbitGeoElementCameraController
  • Renderer
  • Scene
  • SceneProperties
  • SceneView
  • SurfacePlacement

Offline Data

Read more about how to set up the sample's offline data here.

Link Local Location
Model Marker Symbol Data <userhome>/ArcGIS/Runtime/Data/3D/Bristol/Collada/Bristol.dae
GrandCanyon.csv mission data <userhome>/ArcGIS/Runtime/Data/3D/Missions/GrandCanyon.csv
Hawaii.csv mission data <userhome>/ArcGIS/Runtime/Data/3D/Missions/Hawaii.csv
Pyrenees.csv mission data <userhome>/ArcGIS/Runtime/Data/3D/Missions/Pyrenees.csv
Snowdon.csv mission data <userhome>/ArcGIS/Runtime/Data/3D/Missions/Snowdon.csv

Tags

animation, camera, heading, pitch, roll, rotation, visualize

Sample Code

Animate3DSymbols.qmlAnimate3DSymbols.cppAnimate3DSymbols.hLabeledSlider.qmlMissionData.cppMissionData.h
                                                                                                                                                                                                                                    
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
// [WriteFile Name=Animate3DSymbols, Category=Scenes]
// [Legal]
// Copyright 2016 Esri.

// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0

// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// [Legal]

import QtQuick 2.6
import QtQuick.Controls 2.2
import QtQuick.Layouts 1.3
import Esri.Samples 1.0

Animate3DSymbolsSample {
    id: rootRectangle

    property bool following: followButton.checked

    missionFrame: progressSlider.value
    zoom: cameraDistance.value
    angle: cameraAngle.value

    onNextFrameRequested: {
        progressSlider.value = progressSlider.value + 1;
        if (progressSlider.value >= missionSize)
            progressSlider.value = 0;
    }

    Component.onCompleted: {
        missionList.currentIndex = 0;
    }

    SceneView {
        id: sceneView
        objectName: "sceneView"
        anchors.fill: parent

        GridLayout {
            anchors {
                left: parent.left
                right: parent.right
                top: parent.top
                bottom: sceneView.attributionTop
                margins: 10
            }

            columns: 2

            ComboBox {
                id: missionList
                enabled: !playButton.checked
                model: missionsModel
                textRole: "display"
                property real modelWidth: 0
                Layout.minimumWidth: leftPadding + rightPadding + indicator.width + modelWidth

                onModelChanged: {
                    for (let i = 0; i < missionsModel.rowCount(); ++i) {
                        const index = missionsModel.index(i, 0);
                        textMetrics.text = missionsModel.data(index);
                        modelWidth = Math.max(modelWidth, textMetrics.width);
                    }
                }

                onCurrentTextChanged: {
                    changeMission(currentText);
                    progressSlider.value = 0;
                }

                TextMetrics {
                    id: textMetrics
                    font: missionList.font
                }

                Component.onCompleted: missionList.currentTextChanged()
            }

            LabeledSlider {
                id: cameraDistance
                Layout.alignment: Qt.AlignRight
                from: 10.0
                to: 5000.0
                value: 500.0
                text: "zoom"
            }

            RowLayout {
                Button {
                    id: playButton
                    checked: false
                    checkable: true
                    enabled: missionReady
                    text: checked ? "pause" : "play"
                }

                Button {
                    id: followButton
                    Layout.alignment: Qt.AlignRight
                    enabled: missionReady
                    text: checked? "fixed" : "follow "
                    checked: true
                    checkable: true
                    onCheckedChanged: setFollowing(checked);
                }
            }

            LabeledSlider {
                id: cameraAngle
                Layout.alignment: Qt.AlignRight
                from: 0
                to: 180.0
                value: 45.0
                text: "angle"
            }

            LabeledSlider {
                id: progressSlider
                from: 0
                to: missionSize
                enabled : missionReady
                text: (value / missionSize * 100).toLocaleString(Qt.locale(), 'f', 0) + "%"
                handleWidth: progressMetrics.width
                TextMetrics {
                    id: progressMetrics
                    font: progressSlider.font
                    text: "100%"
                }
            }

            LabeledSlider {
                id: animationSpeed
                Layout.alignment: Qt.AlignRight
                from: 1
                to: 100
                value: 50
                text: "speed"
            }

            Rectangle {
                id: mapFrame
                Layout.columnSpan: 2
                Layout.alignment: Qt.AlignLeft | Qt.AlignBottom
                Layout.minimumHeight: parent.height * 0.25
                Layout.minimumWidth: parent.width * 0.3
                color: "black"
                clip: true

                MapView {
                    id: mapView
                    objectName: "mapView"
                    anchors {
                        fill: parent
                        margins: 2
                    }

                    MouseArea {
                        anchors.fill: parent
                        onPressed: mouse.accepted
                        onWheel: wheel.accepted
                    }
                }

                RowLayout {
                    anchors {
                        left: parent.left
                        top: parent.top
                    }
                    spacing: 10

                    Rectangle {
                        Layout.margins: 5
                        height: width
                        width: childrenRect.width
                        clip: true
                        radius: 5

                        opacity: 0.9
                        Image {
                            source: "qrc:/Samples/Scenes/Animate3DSymbols/plus-16-f.png"
                            width: 24
                            height: width

                            MouseArea {
                                anchors.fill: parent
                                onClicked: zoomMapIn()
                            }
                        }
                    }

                    Rectangle {
                        Layout.margins: 5
                        height: width
                        width: childrenRect.width
                        opacity: 0.9
                        clip: true
                        radius: 5

                        Image {
                            source: "qrc:/Samples/Scenes/Animate3DSymbols/minus-16-f.png"
                            width: 24
                            height: width
                            MouseArea {
                                anchors.fill: parent
                                onClicked: zoomMapOut()
                            }
                        }
                    }
                }
            }
        }
    }

    Timer {
        id: timer
        interval: 16.0 + 84 * (animationSpeed.to - animationSpeed.value) / 100.0;
        running: playButton.checked;
        repeat: true
        onTriggered: animate();
    }
}

Your browser is no longer supported. Please upgrade your browser for the best experience. See our browser deprecation post for more details.