ArcObjects Library Reference  

Fly

About the Globe Fly tool Sample

[C#]

Fly.cs

using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Runtime.InteropServices;
using System.Windows.Forms;
using System.Drawing;
using ESRI.ArcGIS.Analyst3D;
using ESRI.ArcGIS.Geometry;
using ESRI.ArcGIS.ADF.BaseClasses;
using ESRI.ArcGIS.ADF.CATIDs;
using ESRI.ArcGIS.GlobeCore;
using ESRI.ArcGIS.esriSystem;
using ESRI.ArcGIS.SystemUI;

namespace GlobeFlyTool
{
    public class Fly : ESRI.ArcGIS.Desktop.AddIns.Tool
    {
        #region DllImport

        [DllImport("user32")]
        public static extern int SetCursor(int hCursor);
        [DllImport("user32")]
        public static extern int GetClientRect(int hwnd, ref  Rectangle lpRect);
        [DllImport("user32")]
        static extern bool GetCursorPos(ref System.Drawing.Point lpPoint);
        [DllImport("user32")]
        public static extern int GetWindowRect(int hwnd, ref  Rectangle lpRect);

        #endregion

        #region Member Variables

        private IGlobe globe;
        private IGlobeDisplay globeDisplay;
        private IGlobeCamera globeCamera;
        private ICamera camera;
        private IScene scene;
        private bool inUse;
        bool bCancel = false;
        bool orbitalFly = false;
        private long mouseX;
        private long mouseY;
        private double motion = 2; //speed of the scene fly through in scene units
        private double distance; //distance between target and observer
        private double currentElevation; //normal fly angles in radians
        private double currentAzimut;  //normal fly angles in radians
        private int speed;
        private System.Windows.Forms.Cursor flyCur;
        private System.Windows.Forms.Cursor moveFlyCur;
        private Microsoft.VisualBasic.Devices.Clock theClock;
        private long lastClock;
        GlobeFlyTool.PointZ observer;
        GlobeFlyTool.PointZ target;
        GlobeFlyTool.PointZ viewVec;

        #endregion

        #region Constructor/Destructor

        public Fly()
        {
            globe = ArcGlobe.Globe;
            scene = globe as IScene;
            globeDisplay = globe.GlobeDisplay;
            camera = globeDisplay.ActiveViewer.Camera;
            globeCamera = camera as IGlobeCamera;
            theClock = new Microsoft.VisualBasic.Devices.Clock();
            flyCur = new System.Windows.Forms.Cursor(GetType().Assembly.GetManifestResourceStream("GlobeFlyTool.Fly.cur"));
            moveFlyCur = new System.Windows.Forms.Cursor(GetType().Assembly.GetManifestResourceStream("GlobeFlyTool.fly1.cur"));
            speed = 0;
        }

        ~Fly()
        {
            flyCur = null;
            moveFlyCur = null;
        }

        #endregion

        protected override void OnUpdate()
        {
            Enabled = ArcGlobe.Application != null;

            if (inUse)
                Cursor = moveFlyCur;
            else
                Cursor = flyCur;
        }

        #region Tool overrides

        protected override void OnMouseUp(ESRI.ArcGIS.Desktop.AddIns.Tool.MouseEventArgs arg)
        {
            if (arg.Button == MouseButtons.Left || arg.Button == MouseButtons.Right)
            {
                if (!inUse)
                {
                    mouseX = arg.X;
                    mouseY = arg.Y;

                    if (speed == 0)
                        StartFlight(arg.X, arg.Y);
                }
                else
                {
                    //Set the speed
                    if (arg.Button == MouseButtons.Left)
                        speed = speed + 1;
                    else if (arg.Button == MouseButtons.Right)
                        speed = speed - 1;
                }
            }
            else
            {
                //EndFlight();
                inUse = false;
                bCancel = true;
            }
        }

        protected override void OnMouseMove(ESRI.ArcGIS.Desktop.AddIns.Tool.MouseEventArgs arg)
        {
            if (!inUse) return;

            mouseX = arg.X;
            mouseY = arg.Y;
        }

        protected override void OnKeyUp(ESRI.ArcGIS.Desktop.AddIns.Tool.KeyEventArgs arg)
        {
            if (inUse == true)
            {
                //Slow down the speed of the fly through
                if (arg.KeyCode == Keys.Down || arg.KeyCode == Keys.Left)
                    motion = motion / 2;
                //Speed up the speed of the fly through
                else if (arg.KeyCode == Keys.Up || arg.KeyCode == Keys.Right)
                    motion = motion * 2;
                else if (arg.KeyCode == Keys.Escape)
                    bCancel = true;

            }
        }

        protected override void OnKeyDown(ESRI.ArcGIS.Desktop.AddIns.Tool.KeyEventArgs arg)
        {
            if (arg.KeyCode == Keys.Escape) //ESC is pressed
            {
                bCancel = true;
            }
        }

        #endregion

        #region Fly Functions

        public void StartFlight(double x, double y)
        {
            inUse = true;

            globeDisplay.IsNavigating = true;
            ESRI.ArcGIS.GlobeCore.esriGlobeCameraOrientationMode camOrientMode =
                globeCamera.OrientationMode;

            orbitalFly = (camOrientMode == ESRI.ArcGIS.GlobeCore.esriGlobeCameraOrientationMode.esriGlobeCameraOrientationLocal) ? true : false;

            IPoint pObs = camera.Observer;
            IPoint pTar = camera.Target;

            observer = new GlobeFlyTool.PointZ(pObs.X, pObs.Y, pObs.Z);
            target = new GlobeFlyTool.PointZ(pTar.X, pTar.Y, pTar.Z);

            viewVec = target - observer;
            distance = viewVec.Norm();

            //avoid center of globe
            if (target.Norm() < 0.25)
            {
                target = target + viewVec;
                distance = distance * 2;
            }

            currentElevation = Math.Atan(viewVec.z / Math.Sqrt((viewVec.x * viewVec.x) + (viewVec.y + viewVec.y)));
            currentAzimut = Math.Atan2(viewVec.y, viewVec.x);//2.26892;//

            //Windows API call to get windows client coordinates
            System.Drawing.Point pt = new System.Drawing.Point();
            bool ans = GetCursorPos(ref pt);
            Rectangle rect = new Rectangle();
            if (GetWindowRect(globeDisplay.ActiveViewer.hWnd, ref rect) == 0) return;

            mouseX = pt.X - rect.Left;
            mouseY = pt.Y - rect.Top;

            if (!orbitalFly)
            {
                globeCamera.OrientationMode = esriGlobeCameraOrientationMode.esriGlobeCameraOrientationGlobal;
            }
            else
            {
                globeCamera.OrientationMode = esriGlobeCameraOrientationMode.esriGlobeCameraOrientationLocal;
            }
            globeCamera.NavigationType = esriGlobeNavigationType.esriGlobeNavigationFree;
            globeCamera.RollFactor = 1.0;

            globeDisplay.IsNavigating = true;
            globeDisplay.IsNavigating = false;
            globeDisplay.IsNavigating = true;

            lastClock = theClock.TickCount;

            //Windows API call to set cursor
            SetCursor(moveFlyCur.Handle.ToInt32());
            //Continue the flight
            Flight();
        }

        public void Flight()
        {
            //speed in scene units
            double motionUnit = (0.000001 + Math.Abs(observer.Norm() - 1.0) / 200.0) * motion;
            //Get IMessageDispatcher interface
            IMessageDispatcher pMessageDispatcher;
            pMessageDispatcher = new MessageDispatcherClass();

            //Set the ESC key to be seen as a cancel action
            pMessageDispatcher.CancelOnClick = false;
            pMessageDispatcher.CancelOnEscPress = true;
            bCancel = false;
            do
            {
                //Get the elapsed time
                long currentClock = theClock.TickCount;
                double lastFrameDuration = (double)(currentClock - lastClock) / 1000;
                lastClock = currentClock;

                if (lastFrameDuration < 0.01)
                    lastFrameDuration = 0.01;

                if (lastFrameDuration > 1)
                    lastFrameDuration = 0.1;

                System.Diagnostics.Debug.Print(lastFrameDuration.ToString());

                //Windows API call to get windows client coordinates
                Rectangle rect = new Rectangle();
                if (GetClientRect(globeDisplay.ActiveViewer.hWnd, ref rect) == 0) return;

                //Get normal vectors
                double dXMouseNormal, dYMouseNormal;

                dXMouseNormal = 2 * ((double)mouseX / (double)(rect.Right - rect.Left)) - 1;
                dYMouseNormal = 2 * ((double)mouseY / (double)(rect.Bottom - rect.Top)) - 1;

                PointZ dir = this.RotateNormal(lastFrameDuration, dXMouseNormal, dYMouseNormal);

                PointZ visTarget = new PointZ(observer.x + distance * dir.x, observer.y + distance * dir.y, observer.z + distance * dir.z);
                target.x = visTarget.x;
                target.y = visTarget.y;
                target.z = visTarget.z;

                if (speed != 0)
                {
                    int speedFactor = (speed > 0) ? (1 << speed) : -(1 << (-speed));

                    //Move the camera in the viewing directions
                    observer.x = observer.x + (lastFrameDuration * (2 ^ speedFactor) * motionUnit * dir.x);
                    observer.y = observer.y + (lastFrameDuration * (2 ^ speedFactor) * motionUnit * dir.y);
                    observer.z = observer.z + (lastFrameDuration * (2 ^ speedFactor) * motionUnit * dir.z);
                    target.x = target.x + (lastFrameDuration * (2 ^ speedFactor) * motionUnit * dir.x);
                    target.y = target.y + (lastFrameDuration * (2 ^ speedFactor) * motionUnit * dir.y);
                    target.z = target.z + (lastFrameDuration * (2 ^ speedFactor) * motionUnit * dir.z);
                }

                ESRI.ArcGIS.GlobeCore.IGlobeViewUtil globeViewUtil = globeCamera as ESRI.ArcGIS.GlobeCore.IGlobeViewUtil;
                double obsLat;
                double obsLon;
                double obsAlt;
                double tarLat;
                double tarLon;
                double tarAlt;

                globeViewUtil.GeocentricToGeographic(observer.x, observer.y, observer.z, out obsLon, out obsLat, out obsAlt);
                globeViewUtil.GeocentricToGeographic(target.x, target.y, target.z, out tarLon, out tarLat, out tarAlt);
                globeCamera.SetObserverLatLonAlt(obsLat, obsLon, obsAlt / 1000);
                globeCamera.SetTargetLatLonAlt(tarLat, tarLon, tarAlt / 1000);

                globeCamera.SetAccurateViewDirection(target.x - observer.x, target.y - observer.y, target.z - observer.z);

                double rollAngle = 0;
                if (speed > 0)
                {
                    rollAngle = 10 * dXMouseNormal * Math.Abs(dXMouseNormal);
                }
                camera.RollAngle = rollAngle;

                //Redraw the scene viewer 
                globeDisplay.RefreshViewers();

                //Dispatch any waiting messages: OnMouseMove / OnMouseUp / OnKeyUp events
                object objCancel = bCancel as object;
                pMessageDispatcher.Dispatch(globeDisplay.ActiveViewer.hWnd, false, out objCancel);

                //End flight if ESC key pressed
                if (bCancel == true)
                    EndFlight();

            }
            while (inUse == true && bCancel == false);

            bCancel = false;
        }

        public void EndFlight()
        {
            inUse = false;
            bCancel = true;
            speed = 0;
            globeDisplay.IsNavigating = false;

            // reposition target
            PointZ currentObs = new PointZ();
            IPoint newTarget = new PointClass();
            currentObs.x = camera.Observer.X;
            currentObs.y = camera.Observer.Y;
            currentObs.z = camera.Observer.Z;

            int orX = 0;
            int orY = 0;
            int width = 0;
            int height = 0;
            camera.GetViewport(ref orX, ref orY, ref width, ref height);

            object obj1;
            object obj2;
            try
            {
                globeDisplay.Locate(globeDisplay.ActiveViewer, width / 2, height / 2, true, true, out newTarget, out obj1, out obj2);
            }
            catch (System.Exception e)
            {
                MessageBox.Show(e.Message);
                MessageBox.Show(e.StackTrace.ToString());
            }

            if (newTarget == null) // no intersection with globe, but don't let the target to be too far
            {
                newTarget = camera.Target;
                PointZ tar = new PointZ(currentObs.x, currentObs.y, currentObs.z);

                double elevObs = tar.Norm() - 1.0;
                if (elevObs <= 0.0001)
                    elevObs = 0.0001;

                PointZ oldTarget = new PointZ(newTarget.X, newTarget.Y, newTarget.Z);
                PointZ dir = (oldTarget - tar);
                double val = dir.Norm();
                if (val > 0.0)
                {
                    dir.x = dir.x * elevObs * 10 / val;
                    dir.y = dir.y * elevObs * 10 / val;
                    dir.z = dir.z * elevObs * 10 / val;
                }

                tar = tar + dir;
                newTarget.X = tar.x;
                newTarget.Y = tar.y;
                newTarget.Z = tar.z;
            }

            ESRI.ArcGIS.GlobeCore.IGlobeViewUtil globeViewUtil = globeCamera as ESRI.ArcGIS.GlobeCore.IGlobeViewUtil;
            double obsLat;
            double obsLon;
            double obsAlt;
            double tarLat;
            double tarLon;
            double tarAlt;
            globeViewUtil.GeocentricToGeographic(currentObs.x, currentObs.y, currentObs.z, out obsLon, out obsLat, out obsAlt);
            globeViewUtil.GeocentricToGeographic(newTarget.X, newTarget.Y, newTarget.Z, out tarLon, out tarLat, out tarAlt);
            globeCamera.SetObserverLatLonAlt(obsLat, obsLon, obsAlt / 1000);
            globeCamera.SetTargetLatLonAlt(tarLat, tarLon, tarAlt / 1000);
            camera.RollAngle = 0;
            camera.PropertiesChanged();
            globeDisplay.RefreshViewers();

            //Windows API call to set cursor
            SetCursor(moveFlyCur.Handle.ToInt32());
        }

        public PointZ RotateNormal(double lastFrameDuration, double mouseXNorm, double mouseYNorm)
        {
            currentElevation = currentElevation - (lastFrameDuration * mouseYNorm * (Math.Abs(mouseYNorm)));
            currentAzimut = currentAzimut - (lastFrameDuration * mouseXNorm * (Math.Abs(mouseXNorm)));

            if (currentElevation > 0.45 * 3.141592)
            {
                currentElevation = 0.45 * 3.141592;
            }
            if (currentElevation < -0.45 * 3.141592)
            {
                currentElevation = -0.45 * 3.141592;
            }
            while (currentAzimut < 0)
            {
                currentAzimut = currentAzimut + (2 * 3.141592);
            }
            while (currentAzimut > 2 * 3.141592)
            {
                currentAzimut = currentAzimut - (2 * 3.141592);
            }

            double x = Math.Cos(currentElevation) * Math.Cos(currentAzimut);
            double y = Math.Cos(currentElevation) * Math.Sin(currentAzimut);
            double z = Math.Sin(currentElevation);

            GlobeFlyTool.PointZ p = new PointZ(x, y, z);
            return p;
        }

        #endregion
    }

}

[Visual Basic .NET]

Fly.vb

Imports Microsoft.VisualBasic
Imports System
Imports System.Collections.Generic
Imports System.Text
Imports System.IO
Imports System.Runtime.InteropServices
Imports System.Windows.Forms
Imports System.Drawing
Imports ESRI.ArcGIS.Analyst3D
Imports ESRI.ArcGIS.Geometry
Imports ESRI.ArcGIS.ADF.BaseClasses
Imports ESRI.ArcGIS.ADF.CATIDs
Imports ESRI.ArcGIS.GlobeCore
Imports ESRI.ArcGIS.esriSystem
Imports ESRI.ArcGIS.SystemUI

Namespace GlobeFlyTool
	Public Class Fly
		Inherits ESRI.ArcGIS.Desktop.AddIns.Tool
		#Region "DllImport"

		<DllImport("user32")> _
		Public Shared Function SetCursor(ByVal hCursor As Integer) As Integer
		End Function
		<DllImport("user32")> _
		Public Shared Function GetClientRect(ByVal hwnd As Integer, ByRef lpRect As Rectangle) As Integer
		End Function
		<DllImport("user32")> _
		Shared Function GetCursorPos(ByRef lpPoint As System.Drawing.Point) As Boolean
		End Function
		<DllImport("user32")> _
		Public Shared Function GetWindowRect(ByVal hwnd As Integer, ByRef lpRect As Rectangle) As Integer
		End Function

		#End Region

		#Region "Member Variables"

		Private globe As IGlobe
		Private globeDisplay As IGlobeDisplay
		Private globeCamera As IGlobeCamera
		Private camera As ICamera
		Private scene As IScene
		Private inUse As Boolean
		Private bCancel As Boolean = False
		Private orbitalFly As Boolean = False
		Private mouseX As Long
		Private mouseY As Long
		Private motion As Double = 2 'speed of the scene fly through in scene units
		Private distance As Double 'distance between target and observer
		Private currentElevation As Double 'normal fly angles in radians
		Private currentAzimut As Double 'normal fly angles in radians
		Private speed As Integer
		Private flyCur As System.Windows.Forms.Cursor
		Private moveFlyCur As System.Windows.Forms.Cursor
		Private theClock As Microsoft.VisualBasic.Devices.Clock
		Private lastClock As Long
		Private observer As GlobeFlyTool.PointZ
		Private target As GlobeFlyTool.PointZ
		Private viewVec As GlobeFlyTool.PointZ

		#End Region

		#Region "Constructor/Destructor"

		Public Sub New()
			globe = ArcGlobe.Globe
			scene = TryCast(globe, IScene)
			globeDisplay = globe.GlobeDisplay
			camera = globeDisplay.ActiveViewer.Camera
			globeCamera = TryCast(camera, IGlobeCamera)
			theClock = New Microsoft.VisualBasic.Devices.Clock()
			flyCur = New System.Windows.Forms.Cursor(Me.GetType().Assembly.GetManifestResourceStream("Fly.cur"))
			moveFlyCur = New System.Windows.Forms.Cursor(Me.GetType().Assembly.GetManifestResourceStream("fly1.cur"))
			speed = 0
		End Sub

		Protected Overrides Sub Finalize()
			flyCur = Nothing
			moveFlyCur = Nothing
		End Sub

		#End Region

		Protected Overrides Sub OnUpdate()
			Enabled = ArcGlobe.Application IsNot Nothing

			If inUse Then
				Cursor = moveFlyCur
			Else
				Cursor = flyCur
			End If
		End Sub

		#Region "Tool overrides"

		Protected Overrides Sub OnMouseUp(ByVal arg As ESRI.ArcGIS.Desktop.AddIns.Tool.MouseEventArgs)
			If arg.Button = MouseButtons.Left OrElse arg.Button = MouseButtons.Right Then
				If (Not inUse) Then
					mouseX = arg.X
					mouseY = arg.Y

					If speed = 0 Then
						StartFlight(arg.X, arg.Y)
					End If
				Else
					'Set the speed
					If arg.Button = MouseButtons.Left Then
						speed = speed + 1
					ElseIf arg.Button = MouseButtons.Right Then
						speed = speed - 1
					End If
				End If
			Else
				'EndFlight();
				inUse = False
				bCancel = True
			End If
		End Sub

		Protected Overrides Sub OnMouseMove(ByVal arg As ESRI.ArcGIS.Desktop.AddIns.Tool.MouseEventArgs)
			If (Not inUse) Then
				Return
			End If

			mouseX = arg.X
			mouseY = arg.Y
		End Sub

		Protected Overrides Sub OnKeyUp(ByVal arg As ESRI.ArcGIS.Desktop.AddIns.Tool.KeyEventArgs)
			If inUse = True Then
				'Slow down the speed of the fly through
				If arg.KeyCode = Keys.Down OrElse arg.KeyCode = Keys.Left Then
					motion = motion / 2
				'Speed up the speed of the fly through
				ElseIf arg.KeyCode = Keys.Up OrElse arg.KeyCode = Keys.Right Then
					motion = motion * 2
				ElseIf arg.KeyCode = Keys.Escape Then
					bCancel = True
				End If

			End If
		End Sub

		Protected Overrides Sub OnKeyDown(ByVal arg As ESRI.ArcGIS.Desktop.AddIns.Tool.KeyEventArgs)
			If arg.KeyCode = Keys.Escape Then 'ESC is pressed
				bCancel = True
			End If
		End Sub

		#End Region

		#Region "Fly Functions"

		Public Sub StartFlight(ByVal x As Double, ByVal y As Double)
			inUse = True

			globeDisplay.IsNavigating = True
			Dim camOrientMode As ESRI.ArcGIS.GlobeCore.esriGlobeCameraOrientationMode = globeCamera.OrientationMode

			orbitalFly = If((camOrientMode = ESRI.ArcGIS.GlobeCore.esriGlobeCameraOrientationMode.esriGlobeCameraOrientationLocal), True, False)

			Dim pObs As IPoint = camera.Observer
			Dim pTar As IPoint = camera.Target

			observer = New GlobeFlyTool.PointZ(pObs.X, pObs.Y, pObs.Z)
			target = New GlobeFlyTool.PointZ(pTar.X, pTar.Y, pTar.Z)

			viewVec = target - observer
			distance = viewVec.Norm()

			'avoid center of globe
			If target.Norm() < 0.25 Then
				target = target + viewVec
				distance = distance * 2
			End If

			currentElevation = Math.Atan(viewVec.z / Math.Sqrt((viewVec.x * viewVec.x) + (viewVec.y + viewVec.y)))
			currentAzimut = Math.Atan2(viewVec.y, viewVec.x) '2.26892;//

			'Windows API call to get windows client coordinates
			Dim pt As New System.Drawing.Point()
			Dim ans As Boolean = GetCursorPos(pt)
			Dim rect As New Rectangle()
			If GetWindowRect(globeDisplay.ActiveViewer.hWnd, rect) = 0 Then
				Return
			End If

			mouseX = pt.X - rect.Left
			mouseY = pt.Y - rect.Top

			If (Not orbitalFly) Then
				globeCamera.OrientationMode = esriGlobeCameraOrientationMode.esriGlobeCameraOrientationGlobal
			Else
				globeCamera.OrientationMode = esriGlobeCameraOrientationMode.esriGlobeCameraOrientationLocal
			End If
			globeCamera.NavigationType = esriGlobeNavigationType.esriGlobeNavigationFree
			globeCamera.RollFactor = 1.0

			globeDisplay.IsNavigating = True
			globeDisplay.IsNavigating = False
			globeDisplay.IsNavigating = True

			lastClock = theClock.TickCount

			'Windows API call to set cursor
			SetCursor(moveFlyCur.Handle.ToInt32())
			'Continue the flight
			Flight()
		End Sub

		Public Sub Flight()
			'speed in scene units
			Dim motionUnit As Double = (0.000001 + Math.Abs(observer.Norm() - 1.0) / 200.0) * motion
			'Get IMessageDispatcher interface
			Dim pMessageDispatcher As IMessageDispatcher
			pMessageDispatcher = New MessageDispatcherClass()

			'Set the ESC key to be seen as a cancel action
			pMessageDispatcher.CancelOnClick = False
			pMessageDispatcher.CancelOnEscPress = True
			bCancel = False
			Do
				'Get the elapsed time
				Dim currentClock As Long = theClock.TickCount
				Dim lastFrameDuration As Double = CDbl(currentClock - lastClock) / 1000
				lastClock = currentClock

				If lastFrameDuration < 0.01 Then
					lastFrameDuration = 0.01
				End If

				If lastFrameDuration > 1 Then
					lastFrameDuration = 0.1
				End If

				System.Diagnostics.Debug.Print(lastFrameDuration.ToString())

				'Windows API call to get windows client coordinates
				Dim rect As New Rectangle()
				If GetClientRect(globeDisplay.ActiveViewer.hWnd, rect) = 0 Then
					Return
				End If

				'Get normal vectors
				Dim dXMouseNormal, dYMouseNormal As Double

				dXMouseNormal = 2 * (CDbl(mouseX) / CDbl(rect.Right - rect.Left)) - 1
				dYMouseNormal = 2 * (CDbl(mouseY) / CDbl(rect.Bottom - rect.Top)) - 1

				Dim dir As PointZ = Me.RotateNormal(lastFrameDuration, dXMouseNormal, dYMouseNormal)

				Dim visTarget As New PointZ(observer.x + distance * dir.x, observer.y + distance * dir.y, observer.z + distance * dir.z)
				target.x = visTarget.x
				target.y = visTarget.y
				target.z = visTarget.z

				If speed <> 0 Then
					Dim speedFactor As Integer = If((speed > 0), (1 << speed), -(1 << (-speed)))

					'Move the camera in the viewing directions
					observer.x = observer.x + (lastFrameDuration * (2 Xor speedFactor) * motionUnit * dir.x)
					observer.y = observer.y + (lastFrameDuration * (2 Xor speedFactor) * motionUnit * dir.y)
					observer.z = observer.z + (lastFrameDuration * (2 Xor speedFactor) * motionUnit * dir.z)
					target.x = target.x + (lastFrameDuration * (2 Xor speedFactor) * motionUnit * dir.x)
					target.y = target.y + (lastFrameDuration * (2 Xor speedFactor) * motionUnit * dir.y)
					target.z = target.z + (lastFrameDuration * (2 Xor speedFactor) * motionUnit * dir.z)
				End If

				Dim globeViewUtil As ESRI.ArcGIS.GlobeCore.IGlobeViewUtil = TryCast(globeCamera, ESRI.ArcGIS.GlobeCore.IGlobeViewUtil)
				Dim obsLat As Double
				Dim obsLon As Double
				Dim obsAlt As Double
				Dim tarLat As Double
				Dim tarLon As Double
				Dim tarAlt As Double

				globeViewUtil.GeocentricToGeographic(observer.x, observer.y, observer.z, obsLon, obsLat, obsAlt)
				globeViewUtil.GeocentricToGeographic(target.x, target.y, target.z, tarLon, tarLat, tarAlt)
				globeCamera.SetObserverLatLonAlt(obsLat, obsLon, obsAlt / 1000)
				globeCamera.SetTargetLatLonAlt(tarLat, tarLon, tarAlt / 1000)

				globeCamera.SetAccurateViewDirection(target.x - observer.x, target.y - observer.y, target.z - observer.z)

				Dim rollAngle As Double = 0
				If speed > 0 Then
					rollAngle = 10 * dXMouseNormal * Math.Abs(dXMouseNormal)
				End If
				camera.RollAngle = rollAngle

				'Redraw the scene viewer 
				globeDisplay.RefreshViewers()

				'Dispatch any waiting messages: OnMouseMove / OnMouseUp / OnKeyUp events
				Dim objCancel As Object = TryCast(bCancel, Object)
				pMessageDispatcher.Dispatch(globeDisplay.ActiveViewer.hWnd, False, objCancel)

				'End flight if ESC key pressed
				If bCancel = True Then
					EndFlight()
				End If

			Loop While inUse = True AndAlso bCancel = False

			bCancel = False
		End Sub

		Public Sub EndFlight()
			inUse = False
			bCancel = True
			speed = 0
			globeDisplay.IsNavigating = False

			' reposition target
			Dim currentObs As New PointZ()
			Dim newTarget As IPoint = New PointClass()
			currentObs.x = camera.Observer.X
			currentObs.y = camera.Observer.Y
			currentObs.z = camera.Observer.Z

			Dim orX As Integer = 0
			Dim orY As Integer = 0
			Dim width As Integer = 0
			Dim height As Integer = 0
			camera.GetViewport(orX, orY, width, height)

			Dim obj1 As Object
			Dim obj2 As Object
			Try
				globeDisplay.Locate(globeDisplay.ActiveViewer, width \ 2, height \ 2, True, True, newTarget, obj1, obj2)
			Catch e As System.Exception
				MessageBox.Show(e.Message)
				MessageBox.Show(e.StackTrace.ToString())
			End Try

			If newTarget Is Nothing Then ' no intersection with globe, but don't let the target to be too far
				newTarget = camera.Target
				Dim tar As New PointZ(currentObs.x, currentObs.y, currentObs.z)

				Dim elevObs As Double = tar.Norm() - 1.0
				If elevObs <= 0.0001 Then
					elevObs = 0.0001
				End If

				Dim oldTarget As New PointZ(newTarget.X, newTarget.Y, newTarget.Z)
				Dim dir As PointZ = (oldTarget - tar)
				Dim val As Double = dir.Norm()
				If val > 0.0 Then
					dir.x = dir.x * elevObs * 10 / val
					dir.y = dir.y * elevObs * 10 / val
					dir.z = dir.z * elevObs * 10 / val
				End If

				tar = tar + dir
				newTarget.X = tar.x
				newTarget.Y = tar.y
				newTarget.Z = tar.z
			End If

			Dim globeViewUtil As ESRI.ArcGIS.GlobeCore.IGlobeViewUtil = TryCast(globeCamera, ESRI.ArcGIS.GlobeCore.IGlobeViewUtil)
			Dim obsLat As Double
			Dim obsLon As Double
			Dim obsAlt As Double
			Dim tarLat As Double
			Dim tarLon As Double
			Dim tarAlt As Double
			globeViewUtil.GeocentricToGeographic(currentObs.x, currentObs.y, currentObs.z, obsLon, obsLat, obsAlt)
			globeViewUtil.GeocentricToGeographic(newTarget.X, newTarget.Y, newTarget.Z, tarLon, tarLat, tarAlt)
			globeCamera.SetObserverLatLonAlt(obsLat, obsLon, obsAlt / 1000)
			globeCamera.SetTargetLatLonAlt(tarLat, tarLon, tarAlt / 1000)
			camera.RollAngle = 0
			camera.PropertiesChanged()
			globeDisplay.RefreshViewers()

			'Windows API call to set cursor
			SetCursor(moveFlyCur.Handle.ToInt32())
		End Sub

		Public Function RotateNormal(ByVal lastFrameDuration As Double, ByVal mouseXNorm As Double, ByVal mouseYNorm As Double) As PointZ
			currentElevation = currentElevation - (lastFrameDuration * mouseYNorm * (Math.Abs(mouseYNorm)))
			currentAzimut = currentAzimut - (lastFrameDuration * mouseXNorm * (Math.Abs(mouseXNorm)))

			If currentElevation > 0.45 * 3.141592 Then
				currentElevation = 0.45 * 3.141592
			End If
			If currentElevation < -0.45 * 3.141592 Then
				currentElevation = -0.45 * 3.141592
			End If
			Do While currentAzimut < 0
				currentAzimut = currentAzimut + (2 * 3.141592)
			Loop
			Do While currentAzimut > 2 * 3.141592
				currentAzimut = currentAzimut - (2 * 3.141592)
			Loop

			Dim x As Double = Math.Cos(currentElevation) * Math.Cos(currentAzimut)
			Dim y As Double = Math.Cos(currentElevation) * Math.Sin(currentAzimut)
			Dim z As Double = Math.Sin(currentElevation)

			Dim p As GlobeFlyTool.PointZ = New PointZ(x, y, z)
			Return p
		End Function

		#End Region
	End Class

End Namespace