Cesium for Unity 1.15.2
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
333 // Set the controller's roll to 0.0
334 Vector3 angles = this.transform.eulerAngles;
335 angles.z = 0.0f;
336 this.transform.eulerAngles = angles;
337
338 this._globeAnchor.adjustOrientationForGlobeWhenMoving = true;
339 this._globeAnchor.detectTransformChanges = true;
340
341 if (this._cameraController != null)
342 {
343 this._cameraController.enableMovement = true;
344 this._cameraController.enableRotation = true;
345 }
346
347 if (this.OnFlightInterrupted != null)
348 {
349 this.OnFlightInterrupted();
350 }
351 }
352
353 private void ComputeFlightPath(
354 double3 sourceECEF,
355 double3 destinationECEF,
356 float yawAtDestination,
357 float pitchAtDestination)
358 {
359 // The source and destination rotations are expressed in East-Up-North coordinates.
360 pitchAtDestination = Mathf.Clamp(pitchAtDestination, -89.99f, 89.99f);
361 this._sourceRotation = this._globeAnchor.transform.rotation;
362 this._destinationRotation = Quaternion.Euler(pitchAtDestination, yawAtDestination, 0.0f);
363 this._destinationECEF = destinationECEF;
364
365 this._flightPath = CesiumSimplePlanarEllipsoidCurve.FromCenteredFixedCoordinates(
366 this._georeference.ellipsoid,
367 sourceECEF,
368 destinationECEF);
369 this._flightPathLength = math.length(sourceECEF - destinationECEF);
370
371 this._maxHeight = 0.0;
372 if (this._flyToAltitudeProfileCurve != null && this._flyToMaximumAltitudeCurve.length > 0)
373 {
374 this._maxHeight = 30000.0;
375 if (this._flyToMaximumAltitudeCurve != null && this._flyToMaximumAltitudeCurve.length > 0)
376 {
377 this._maxHeight = this._flyToMaximumAltitudeCurve.Evaluate((float)this._flightPathLength);
378 }
379 }
380
381 this._previousPositionECEF = sourceECEF;
382 this._flyingToLocation = true;
383 }
384
402 double3 destination,
403 float yawAtDestination,
404 float pitchAtDestination,
405 bool canInterruptByMoving)
406 {
407 if (this._flyingToLocation)
408 {
409 return;
410 }
411
412 pitchAtDestination = Mathf.Clamp(pitchAtDestination, -89.99f, 89.99f);
413
414 // Compute source location in ECEF
415 double3 source = this._globeAnchor.positionGlobeFixed;
416
417 this.ComputeFlightPath(source, destination, yawAtDestination, pitchAtDestination);
418
419 // Indicate that the controller will be flying from now
420 this._flyingToLocation = true;
421 this._canInterruptFlight = canInterruptByMoving;
422 this._globeAnchor.adjustOrientationForGlobeWhenMoving = false;
423 this._globeAnchor.detectTransformChanges = false;
424
425 if (this._cameraController != null)
426 {
427 this._cameraController.enableMovement = canInterruptByMoving;
428 this._cameraController.enableRotation = false;
429 }
430 }
431
449 Vector3 destination,
450 float yawAtDestination,
451 float pitchAtDestination,
452 bool canInterruptByMoving)
453 {
455 new double3()
456 {
457 x = destination.x,
458 y = destination.y,
459 z = destination.z,
460 },
461 yawAtDestination,
462 pitchAtDestination,
463 canInterruptByMoving);
464 }
465
484 double3 destination,
485 float yawAtDestination,
486 float pitchAtDestination,
487 bool canInterruptByMoving)
488 {
489 double3 destinationECEF =
490 this._georeference.ellipsoid.LongitudeLatitudeHeightToCenteredFixed(destination);
491
493 destinationECEF,
494 yawAtDestination,
495 pitchAtDestination,
496 canInterruptByMoving);
497 }
498
517 Vector3 destination,
518 float yawAtDestination,
519 float pitchAtDestination,
520 bool canInterruptByMoving)
521 {
522 double3 destinationCoordinates = new double3()
523 {
524 x = destination.x,
525 y = destination.y,
526 z = destination.z
527 };
528 double3 destinationECEF =
530 destinationCoordinates);
531
533 destinationECEF,
534 yawAtDestination,
535 pitchAtDestination,
536 canInterruptByMoving);
537 }
538 }
539}
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.