Cesium for Unity 1.17.0
Loading...
Searching...
No Matches
CesiumFlyToController.cs
Go to the documentation of this file.
1using System;
2using Unity.Mathematics;
3using UnityEngine;
4
5namespace CesiumForUnity
6{
16 [DisallowMultipleComponent]
17 [AddComponentMenu("Cesium/Cesium Fly To Controller")]
18 [IconAttribute("Packages/com.cesium.unity/Editor/Resources/Cesium-24x24.png")]
19 public class CesiumFlyToController : MonoBehaviour
20 {
21 [SerializeField]
22 private AnimationCurve _flyToAltitudeProfileCurve;
23
33 public AnimationCurve flyToAltitudeProfileCurve
34 {
35 get => this._flyToAltitudeProfileCurve;
36 set => this._flyToAltitudeProfileCurve = value;
37 }
38
39 [SerializeField]
40 private AnimationCurve _flyToProgressCurve;
41
46 public AnimationCurve flyToProgressCurve
47 {
48 get => this._flyToProgressCurve;
49 set => this._flyToProgressCurve = value;
50 }
51
52 [SerializeField]
53 private AnimationCurve _flyToMaximumAltitudeCurve;
54
63 public AnimationCurve flyToMaximumAltitudeCurve
64 {
65 get => this._flyToMaximumAltitudeCurve;
66 set => this._flyToMaximumAltitudeCurve = value;
67 }
68
69 [SerializeField]
70 [Min(0.0f)]
71 private double _flyToDuration = 5.0;
72
76 public double flyToDuration
77 {
78 get => this._flyToDuration;
79 set => this._flyToDuration = Math.Max(value, 0.0);
80 }
81
82 #region Deprecated Functionality
83 [Min(0.0f)]
84 private double _flyToGranularityDegrees = 0.01;
85
96 [Obsolete("CesiumFlyToController no longer works using keypoints. This value has no effect.")]
98 {
99 get => this._flyToGranularityDegrees;
100 set
101 {
102 this._flyToGranularityDegrees = Math.Max(value, 0.0);
103 if (this._flyToGranularityDegrees == 0.0)
104 {
105 Debug.LogWarning(
106 "FlyToGranularityDegrees must be set to a non-zero value. " +
107 "Otherwise, CesiumFlyToController will not fly to any " +
108 "specified location.");
109 }
110 }
111 }
112 #endregion
113
117 public delegate void CompletedFlightDelegate();
118
123
127 public delegate void InterruptedFlightDelegate();
128
133
134 private CesiumGeoreference _georeference;
135 private CesiumGlobeAnchor _globeAnchor;
136 private CesiumSimplePlanarEllipsoidCurve _flightPath;
137 private double _flightPathLength;
138
139 private CesiumCameraController _cameraController;
140
141 private Quaternion _sourceRotation;
142 private Quaternion _destinationRotation;
143
144 private double3 _destinationECEF;
145 private double3 _previousPositionECEF;
146
147 private double _maxHeight;
148
149 private double _currentFlyToTime = 0.0;
150
151 private bool _flyingToLocation = false;
152 private bool _canInterruptFlight = true;
153
154 void Awake()
155 {
156 this._georeference = this.gameObject.GetComponentInParent<CesiumGeoreference>();
157 if (this._georeference == null)
158 {
159 Debug.LogError(
160 "CesiumFlyToController must be nested under a game object " +
161 "with a CesiumGeoreference.");
162 }
163
164 this._globeAnchor = this.gameObject.GetComponentInParent<CesiumGlobeAnchor>();
165 if (this._globeAnchor == null)
166 {
167 Debug.LogError("CesiumFlyToController expects a CesiumGlobeAnchor to be attached to itself or to a parent");
168 }
169
170 CesiumOriginShift originShift = this._globeAnchor.GetComponent<CesiumOriginShift>();
171 if (originShift == null)
172 {
173 Debug.LogError("CesiumFlyToController expects a CesiumOriginShift to be attached to the CesiumGlobeAnchor above it");
174 }
175
176 // If a CesiumCameraController is present, CesiumFlyToController will account for
177 // its inputs and modify it during flights.
178 this._cameraController = this.gameObject.GetComponent<CesiumCameraController>();
179 }
180
181 void Update()
182 {
183 if (this._flyingToLocation)
184 {
185 this.HandleFlightStep(Time.deltaTime);
186 }
187 }
188
189#if UNITY_EDITOR
190 // Ensures required components are present in the editor.
191 private void Reset()
192 {
193 CesiumGlobeAnchor anchor = this.gameObject.GetComponentInParent<CesiumGlobeAnchor>();
194 if(anchor == null)
195 {
196 anchor = this.gameObject.AddComponent<CesiumGlobeAnchor>();
197 Debug.LogWarning("CesiumFlyToController missing a CesiumGlobeAnchor - adding");
198 }
199
200 CesiumOriginShift origin = anchor.GetComponent<CesiumOriginShift>();
201 if(origin == null)
202 {
203 origin = anchor.gameObject.AddComponent<CesiumOriginShift>();
204 Debug.LogWarning("CesiumFlyToController missing a CesiumOriginShift - adding");
205 }
206 }
207#endif
208
219 private bool DetectMovementInput()
220 {
221 double3 currentPositionECEF = this._globeAnchor.positionGlobeFixed;
222 double distanceSquared = math.lengthsq(currentPositionECEF - this._previousPositionECEF);
223 const double distanceConsideredMovement = 1e-6; // 1/1000 of a millimeter
224 return distanceSquared >= (distanceConsideredMovement * distanceConsideredMovement);
225 }
226
246 private void HandleFlightStep(float deltaTime)
247 {
248 if (!this._flyingToLocation || this._flightPath == null)
249 {
250 return;
251 }
252
253 if (this._georeference == null)
254 {
255 this._flyingToLocation = false;
256 return;
257 }
258
259 if (this._canInterruptFlight && this.DetectMovementInput())
260 {
261 this.InterruptFlight();
262 return;
263 }
264
265 this._currentFlyToTime += deltaTime;
266
267 double flyPercentage = 0.0;
268 if (this._currentFlyToTime >= this._flyToDuration)
269 {
270 flyPercentage = 1.0;
271 }
272 else if (this._flyToProgressCurve != null && this._flyToProgressCurve.length > 0)
273 {
274 // Sample the progress curve if we have one
275 flyPercentage = math.clamp(this._flyToProgressCurve.Evaluate((float)(this._currentFlyToTime / this._flyToDuration)), 0.0, 1.0);
276 }
277 else
278 {
279 flyPercentage = this._currentFlyToTime / this._flyToDuration;
280 }
281
282 // If we're have it to the end of the flight, or if the flight we're taking isn't actually moving or rotating us at all, we're done.
283 if (flyPercentage == 1.0 || (this._flightPathLength == 0.0 && this._sourceRotation == this._destinationRotation))
284 {
285 this.CompleteFlight();
286 return;
287 }
288
289 // Calculate the height above the surface. If we have a profile curve, use it as well.
290 double altituteOffset = 0.0;
291 if (this._maxHeight != 0.0 && this.flyToAltitudeProfileCurve != null && this.flyToAltitudeProfileCurve.length > 0)
292 {
293 double curveOffset = this._maxHeight * this.flyToAltitudeProfileCurve.Evaluate((float)flyPercentage);
294 altituteOffset += curveOffset;
295 }
296
297 // Update position.
298 double3 currentPosition = this._flightPath.GetPosition(flyPercentage, altituteOffset);
299 this._previousPositionECEF = currentPosition;
300 this._globeAnchor.positionGlobeFixed = currentPosition;
301
302 Quaternion currentQuat = Quaternion.Slerp(this._sourceRotation, this._destinationRotation, (float)flyPercentage);
303 this._globeAnchor.rotationEastUpNorth = currentQuat;
304 }
305
306 private void CompleteFlight()
307 {
308 this._globeAnchor.positionGlobeFixed = _destinationECEF;
309 this._globeAnchor.rotationEastUpNorth = this._destinationRotation;
310
311 this._flyingToLocation = false;
312 this._currentFlyToTime = 0.0;
313
314 this._globeAnchor.adjustOrientationForGlobeWhenMoving = true;
315 this._globeAnchor.detectTransformChanges = true;
316
317 if (this._cameraController != null)
318 {
319 this._cameraController.enableMovement = true;
320 this._cameraController.enableRotation = true;
321 }
322
323 if (this.OnFlightComplete != null)
324 {
325 this.OnFlightComplete();
326 }
327 }
328
329 private void InterruptFlight()
330 {
331 this._flyingToLocation = false;
332 this._currentFlyToTime = 0.0;
333
334 // Set the controller's roll to 0.0
335 Vector3 angles = this.transform.eulerAngles;
336 angles.z = 0.0f;
337 this.transform.eulerAngles = angles;
338
339 this._globeAnchor.adjustOrientationForGlobeWhenMoving = true;
340 this._globeAnchor.detectTransformChanges = true;
341
342 if (this._cameraController != null)
343 {
344 this._cameraController.enableMovement = true;
345 this._cameraController.enableRotation = true;
346 }
347
348 if (this.OnFlightInterrupted != null)
349 {
350 this.OnFlightInterrupted();
351 }
352 }
353
354 private void ComputeFlightPath(
355 double3 sourceECEF,
356 double3 destinationECEF,
357 float yawAtDestination,
358 float pitchAtDestination)
359 {
360 // The source and destination rotations are expressed in East-Up-North coordinates.
361 pitchAtDestination = Mathf.Clamp(pitchAtDestination, -89.99f, 89.99f);
362 this._sourceRotation = this._globeAnchor.transform.rotation;
363 this._destinationRotation = Quaternion.Euler(pitchAtDestination, yawAtDestination, 0.0f);
364 this._destinationECEF = destinationECEF;
365
366 this._flightPath = CesiumSimplePlanarEllipsoidCurve.FromCenteredFixedCoordinates(
367 this._georeference.ellipsoid,
368 sourceECEF,
369 destinationECEF);
370 this._flightPathLength = math.length(sourceECEF - destinationECEF);
371
372 this._maxHeight = 0.0;
373 if (this._flyToAltitudeProfileCurve != null && this._flyToMaximumAltitudeCurve.length > 0)
374 {
375 this._maxHeight = 30000.0;
376 if (this._flyToMaximumAltitudeCurve != null && this._flyToMaximumAltitudeCurve.length > 0)
377 {
378 this._maxHeight = this._flyToMaximumAltitudeCurve.Evaluate((float)this._flightPathLength);
379 }
380 }
381
382 this._previousPositionECEF = sourceECEF;
383 this._flyingToLocation = true;
384 }
385
403 double3 destination,
404 float yawAtDestination,
405 float pitchAtDestination,
406 bool canInterruptByMoving)
407 {
408 if (this._flyingToLocation)
409 {
410 return;
411 }
412
413 pitchAtDestination = Mathf.Clamp(pitchAtDestination, -89.99f, 89.99f);
414
415 // Compute source location in ECEF
416 double3 source = this._globeAnchor.positionGlobeFixed;
417
418 this.ComputeFlightPath(source, destination, yawAtDestination, pitchAtDestination);
419
420 // Indicate that the controller will be flying from now
421 this._flyingToLocation = true;
422 this._currentFlyToTime = 0.0;
423 this._canInterruptFlight = canInterruptByMoving;
424 this._globeAnchor.adjustOrientationForGlobeWhenMoving = false;
425 this._globeAnchor.detectTransformChanges = false;
426
427 if (this._cameraController != null)
428 {
429 this._cameraController.enableMovement = canInterruptByMoving;
430 this._cameraController.enableRotation = false;
431 }
432 }
433
451 Vector3 destination,
452 float yawAtDestination,
453 float pitchAtDestination,
454 bool canInterruptByMoving)
455 {
457 new double3()
458 {
459 x = destination.x,
460 y = destination.y,
461 z = destination.z,
462 },
463 yawAtDestination,
464 pitchAtDestination,
465 canInterruptByMoving);
466 }
467
486 double3 destination,
487 float yawAtDestination,
488 float pitchAtDestination,
489 bool canInterruptByMoving)
490 {
491 double3 destinationECEF =
492 this._georeference.ellipsoid.LongitudeLatitudeHeightToCenteredFixed(destination);
493
495 destinationECEF,
496 yawAtDestination,
497 pitchAtDestination,
498 canInterruptByMoving);
499 }
500
519 Vector3 destination,
520 float yawAtDestination,
521 float pitchAtDestination,
522 bool canInterruptByMoving)
523 {
524 double3 destinationCoordinates = new double3()
525 {
526 x = destination.x,
527 y = destination.y,
528 z = destination.z
529 };
530 double3 destinationECEF =
532 destinationCoordinates);
533
535 destinationECEF,
536 yawAtDestination,
537 pitchAtDestination,
538 canInterruptByMoving);
539 }
540 }
541}
A camera controller that can easily move around and view the globe while maintaining a sensible orien...
partial double3 LongitudeLatitudeHeightToCenteredFixed(double3 longitudeLatitudeHeight)
Convert longitude, latitude, and height to Ellipsoid-Centered, Ellipsoid-Fixed (ECEF) coordinates.
A controller that can smoothly fly to locations around the globe while offering control over the char...
void FlyToLocationLongitudeLatitudeHeight(Vector3 destination, float yawAtDestination, float pitchAtDestination, bool canInterruptByMoving)
Begin a smooth flight to the given WGS84 longitude in degrees (x), latitude in degrees (y),...
delegate void InterruptedFlightDelegate()
Encapsulates a method that is called whenever the controller's flight is interrupted.
double flyToDuration
The length in seconds that the flight should last.
InterruptedFlightDelegate OnFlightInterrupted
An event that is raised when the controller's flight is interrupted.
AnimationCurve flyToAltitudeProfileCurve
A curve that dictates what percentage of the max altitude the controller should take at a given time ...
void FlyToLocationEarthCenteredEarthFixed(double3 destination, float yawAtDestination, float pitchAtDestination, bool canInterruptByMoving)
Begin a smooth flight to the given Earth-Centered, Earth-Fixed (ECEF) destination such that the contr...
AnimationCurve flyToMaximumAltitudeCurve
A curve that dictates the maximum altitude at each point along the curve.
double flyToGranularityDegrees
The granularity in degrees with which keypoints should be generated for the flight interpolation.
delegate void CompletedFlightDelegate()
Encapsulates a method that is called whenever the controller finishes flying.
void FlyToLocationEarthCenteredEarthFixed(Vector3 destination, float yawAtDestination, float pitchAtDestination, bool canInterruptByMoving)
Begin a smooth flight to the given Earth-Centered, Earth-Fixed (ECEF) destination such that the contr...
CompletedFlightDelegate OnFlightComplete
An event that is raised when the controller finishes flying.
void FlyToLocationLongitudeLatitudeHeight(double3 destination, float yawAtDestination, float pitchAtDestination, bool canInterruptByMoving)
Begin a smooth flight to the given WGS84 longitude in degrees (x), latitude in degrees (y),...
AnimationCurve flyToProgressCurve
A curve that is used to determine the progress percentage for all the other curves.
Controls how global geospatial coordinates are mapped to coordinates in the Unity scene.
CesiumEllipsoid ellipsoid
The CesiumEllipsoid that is referenced.
Anchors this game object to the globe.
double3 positionGlobeFixed
Gets or sets the game object's position in the Earth-Centered, Earth-Fixed (ECEF) coordinates in mete...
Describes a curve that's a section of an ellipse that lies on a plane intersecting the center of the ...
partial double3 GetPosition(double percentage, double additionalHeight=0.0)
Samples the curve at the given percentage of its length.