Commits

Ron Huang committed 98422a1

Utilized Motion API to point to Makkah.

Comments (0)

Files changed (9)

WhereIsMakkah/Lang/AppResources.Designer.cs

         }
         
         /// <summary>
-        ///   Looks up a localized string similar to Large arrow is pointing toward Makkah..
+        ///   Looks up a localized string similar to Arrow is pointing toward Makkah..
         /// </summary>
         public static string FeedbackReadyLabel {
             get {

WhereIsMakkah/Lang/AppResources.resx

     <value>Location data is not available.</value>
   </data>
   <data name="FeedbackReadyLabel" xml:space="preserve">
-    <value>Large arrow is pointing toward Makkah.</value>
+    <value>Arrow is pointing toward Makkah.</value>
   </data>
   <data name="FeedbackStartingLabel" xml:space="preserve">
     <value>Starting location service. This may take a while.</value>

WhereIsMakkah/MainPage.xaml

 <phone:PhoneApplicationPage
-    x:Class="WhereIsMakkah.MainPage"
     xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
     xmlns:phone="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone"
     xmlns:es="clr-namespace:Microsoft.Expression.Shapes;assembly=Microsoft.Expression.Drawing"
     xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
     xmlns:GalaSoft_MvvmLight_Command="clr-namespace:GalaSoft.MvvmLight.Command;assembly=GalaSoft.MvvmLight.Extras.WP71"
-    FontFamily="{StaticResource PhoneFontFamilyNormal}"
-    FontSize="{StaticResource PhoneFontSizeNormal}"
-    Foreground="{StaticResource PhoneForegroundBrush}"
-    SupportedOrientations="PortraitOrLandscape"
+    xmlns:WhereIsMakkah_Util="clr-namespace:WhereIsMakkah.Util"
+    x:Class="WhereIsMakkah.MainPage"
+    SupportedOrientations="Portrait"
     Orientation="Portrait"
     mc:Ignorable="d"
     d:DesignWidth="480"
     d:DesignHeight="696"
     shell:SystemTray.IsVisible="True"
-    DataContext="{Binding Main, Source={StaticResource Locator}}">
+    >
 
 	<phone:PhoneApplicationPage.Resources>
+		<WhereIsMakkah_Util:BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter"/>
 		<Storyboard x:Name="IndeterminateArrow" RepeatBehavior="Forever">
 			<DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Projection).(PlaneProjection.RotationZ)" Storyboard.TargetName="DirectionArrow">
 				<EasingDoubleKeyFrame KeyTime="0:0:0.5" Value="2"/>
 			</DoubleAnimation>
 		</Storyboard>
 	</phone:PhoneApplicationPage.Resources>
+	<phone:PhoneApplicationPage.FontFamily>
+		<StaticResource ResourceKey="PhoneFontFamilyNormal"/>
+	</phone:PhoneApplicationPage.FontFamily>
+	<phone:PhoneApplicationPage.FontSize>
+		<StaticResource ResourceKey="PhoneFontSizeNormal"/>
+	</phone:PhoneApplicationPage.FontSize>
+	<phone:PhoneApplicationPage.Foreground>
+		<StaticResource ResourceKey="PhoneForegroundBrush"/>
+	</phone:PhoneApplicationPage.Foreground>
+	<phone:PhoneApplicationPage.DataContext>
+		<Binding Path="Main" Source="{StaticResource Locator}"/>
+	</phone:PhoneApplicationPage.DataContext>
 	<i:Interaction.Triggers>
 		<i:EventTrigger>
 			<GalaSoft_MvvmLight_Command:EventToCommand Command="{Binding SensorStartCommand, Mode=OneWay}"/>
 		Background="Transparent">
 		<Grid.RowDefinitions>
 			<RowDefinition Height="Auto" />
-			<RowDefinition Height="Auto"/>
 			<RowDefinition Height="*" />
 			<RowDefinition Height="Auto"/>
 			<RowDefinition Height="Auto"/>
 				Text="{Binding LocalizedResources.ApplicationTitle, Source={StaticResource LocalizedStrings}}"
 				Style="{StaticResource PhoneTextNormalStyle}" FontSize="{StaticResource PhoneFontSizeLarge}" />
 		</StackPanel>
-		<StackPanel Margin="{StaticResource PhoneMargin}" Grid.Row="1" Orientation="Horizontal" HorizontalAlignment="Center">
-			<!--ContentPanel - place additional content here-->
-			<es:BlockArrow Height="30" Orientation="Up" Stroke="{StaticResource PhoneAccentBrush}" UseLayoutRounding="False" Width="30" StrokeThickness="{StaticResource PhoneStrokeThickness}"/>
-			<!--ContentPanel - place additional content here-->
-			<TextBlock Text="{Binding LocalizedResources.InstructionLabel, Source={StaticResource LocalizedStrings}}" Margin="{StaticResource PhoneMargin}" TextAlignment="Center"/>
-		</StackPanel>
 
 		<!--ContentPanel - place additional content here-->
-		<es:BlockArrow x:Name="DirectionArrow" Fill="{StaticResource PhoneAccentBrush}" Orientation="Up" UseLayoutRounding="False" Width="200" Grid.Row="2" Height="250">
+		<es:BlockArrow x:Name="DirectionArrow" Fill="{StaticResource PhoneAccentBrush}" Orientation="Up" UseLayoutRounding="False" Width="400" Grid.Row="1" Height="500" Visibility="{Binding DirectionDetermined, Converter={StaticResource BooleanToVisibilityConverter}}">
 			<es:BlockArrow.Projection>
-				<PlaneProjection RotationX="0" RotationY="0" RotationZ="{Binding CurrentRotationZ}"/>
+				<Matrix3DProjection ProjectionMatrix="{Binding CurrentMatrix}"/>
 			</es:BlockArrow.Projection>
 		</es:BlockArrow>
-		<TextBlock TextWrapping="Wrap" Text="{Binding Feedback, Mode=OneWay}" Margin="{StaticResource PhoneMargin}" d:LayoutOverrides="Width" Grid.Row="3" HorizontalAlignment="Center"/>
-		<TextBlock TextWrapping="Wrap" Text="{Binding DistanceLabel, Mode=OneWay}" Margin="{StaticResource PhoneMargin}" d:LayoutOverrides="Width" Grid.Row="4" HorizontalAlignment="Center"/>
+		<TextBlock TextWrapping="Wrap" Text="{Binding Feedback, Mode=OneWay}" Margin="{StaticResource PhoneMargin}" d:LayoutOverrides="Width" Grid.Row="2" HorizontalAlignment="Center"/>
+		<TextBlock TextWrapping="Wrap" Text="{Binding DistanceLabel, Mode=OneWay}" Margin="{StaticResource PhoneMargin}" d:LayoutOverrides="Width" Grid.Row="3" HorizontalAlignment="Center"/>
 	</Grid>
 
 

WhereIsMakkah/MainPage.xaml.cs

 using System;
-using System.Windows.Media;
 using System.Windows.Data;
-using System.Windows.Media.Animation;
 using System.Text;
 using Microsoft.Phone.Controls;
 using Microsoft.Phone.Shell;
                     SystemTray.SetProgressIndicator(this, progressIndicator);
 
                     // Bind progress indicator to Busy property
-                    var binding = new Binding("Busy") { Source = this.LayoutRoot.DataContext };
+                    var binding = new Binding("LocationDetermined") { Source = this.LayoutRoot.DataContext, Converter = new InverseBooleanConverter() };
                     BindingOperations.SetBinding(progressIndicator, ProgressIndicator.IsVisibleProperty, binding);
 
                     // Localize the text on application bar.
 
                     SystemTray.SetProgressIndicator(this, null);
 
-                    // Unbind progress indicator to Busy property
+                    // Unbind progress indicator to LocationDetermined property
                     BindingOperations.SetBinding(progressIndicator, ProgressIndicator.IsVisibleProperty, null);
                 };
 
-            Messenger.Default.Register<AnimateArrowMessage>(this, (msg) => ReceiveMessage(msg));
             Messenger.Default.Register<GoToPageMessage>(this, (msg) => ReceiveMessage(msg));
         }
 
             }
         }
 
-        private object ReceiveMessage(AnimateArrowMessage msg)
-        {
-            if (msg.Run)
-            {
-                if (msg.Indeterminate)
-                {
-                    if (IndeterminateArrow.GetCurrentState() != ClockState.Stopped)
-                    {
-                        return null;
-                    }
-
-                    var vm = DataContext as MainViewModel;
-                    var anim = IndeterminateArrow.Children[0] as DoubleAnimationUsingKeyFrames;
-                    anim.KeyFrames[0].Value = vm.CurrentRotationZ + 2.0;
-                    anim.KeyFrames[1].Value = vm.CurrentRotationZ - 2.0;
-                    anim.KeyFrames[2].Value = vm.CurrentRotationZ;
-
-                    IndeterminateArrow.Begin();
-                }
-                else
-                {
-                    if (DeterminateArrow.GetCurrentState() != ClockState.Stopped)
-                    {
-                        return null;
-                    }
-
-                    var vm = DataContext as MainViewModel;
-                    var anim = DeterminateArrow.Children[0] as DoubleAnimation;
-                    anim.From = vm.CurrentRotationZ;
-                    anim.To = msg.DestinationZ;
-                    vm.CurrentRotationZ = msg.DestinationZ;
-
-                    DeterminateArrow.Begin();
-                }
-            }
-            else
-            {
-                if (IndeterminateArrow.GetCurrentState() != ClockState.Stopped)
-                {
-                    IndeterminateArrow.Stop();
-                }
-                if (DeterminateArrow.GetCurrentState() != ClockState.Stopped)
-                {
-                    DeterminateArrow.Stop();
-                }
-            }
-            return null;
-        }
-
         private object ReceiveMessage(GoToPageMessage msg)
         {
             StringBuilder sb = new StringBuilder("/View/");

WhereIsMakkah/Util/AnimateArrowMessage.cs

-using System;
-
-namespace WhereIsMakkah.Util
-{
-    public class AnimateArrowMessage
-    {
-        public bool Run { get; set; }
-        public bool Indeterminate { get; set; }
-        public double DestinationZ { get; set; }
-    }
-}

WhereIsMakkah/Util/BooleanToVisibilityConverter.cs

+using System;
+using System.Windows;
+using System.Windows.Data;
+using System.Globalization;
+
+namespace WhereIsMakkah.Util
+{
+    public class BooleanToVisibilityConverter : IValueConverter
+    {
+        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+        {
+            if (value == null)
+                return Visibility.Collapsed;
+
+            var isVisible = (bool)value;
+            return isVisible ? Visibility.Visible : Visibility.Collapsed;
+        }
+
+        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+        {
+            var visiblity = (Visibility)value;
+            return visiblity == Visibility.Visible;
+        }
+    }
+}

WhereIsMakkah/Util/InverseBooleanConverter.cs

+using System;
+using System.Windows.Data;
+using System.Globalization;
+
+namespace WhereIsMakkah.Util
+{
+    public class InverseBooleanConverter : IValueConverter
+    {
+        #region IValueConverter Members
+
+        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+        {
+            if (targetType != typeof(bool))
+                throw new InvalidOperationException("The target must be a boolean");
+
+            return !(bool)value;
+        }
+
+        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+        {
+            throw new NotSupportedException();
+        }
+
+        #endregion
+    }
+}

WhereIsMakkah/ViewModel/MainViewModel.cs

 using System;
-using System.Windows;
 using System.Device.Location;
-using System.Windows.Threading;
+using System.Windows.Media.Media3D;
 using Microsoft.Devices.Sensors;
 using Microsoft.Xna.Framework;
-using Microsoft.Phone.Shell;
 using GalaSoft.MvvmLight;
 using GalaSoft.MvvmLight.Threading;
 using GalaSoft.MvvmLight.Command;
     public class MainViewModel : ViewModelBase
     {
         /// <summary>
-        /// The <see cref="CurrentRotationZ" /> property's name.
+        /// The <see cref="OffsetFromNorthMatrix" /> property's name.
         /// </summary>
-        public const string CurrentRotationZPropertyName = "CurrentRotationZ";
+        public const string OffsetFromNorthMatrixPropertyName = "OffsetFromNorthMatrix";
 
-        private double _currentRotationZ = 0.0;
+        private Matrix3D _offsetFromNorthMatrix = Matrix3D.Identity;
 
         /// <summary>
-        /// Gets the CurrentRotationZ property.
+        /// Gets the OffsetFromNorthMatrix property.
+        /// Changes to that property's value raise the PropertyChanged event. 
+        /// This property's value is broadcasted by the Messenger's default instance when it changes.
+        /// </summary>
+        public Matrix3D OffsetFromNorthMatrix
+        {
+            get
+            {
+                return _offsetFromNorthMatrix;
+            }
+
+            set
+            {
+                if (_offsetFromNorthMatrix == value)
+                {
+                    return;
+                }
+
+                var oldValue = _offsetFromNorthMatrix;
+                _offsetFromNorthMatrix = value;
+
+                // Update bindings, no broadcast
+                RaisePropertyChanged(OffsetFromNorthMatrixPropertyName);
+            }
+        }
+
+        /// <summary>
+        /// The <see cref="LocationDetermined" /> property's name.
+        /// </summary>
+        public const string LocationDeterminedPropertyName = "LocationDetermined";
+
+        private bool _locationDetermined = false;
+
+        /// <summary>
+        /// Gets the LocationDetermined property.
+        /// Changes to that property's value raise the PropertyChanged event. 
+        /// This property's value is broadcasted by the Messenger's default instance when it changes.
+        /// </summary>
+        public bool LocationDetermined
+        {
+            get
+            {
+                return _locationDetermined;
+            }
+
+            set
+            {
+                if (_locationDetermined == value)
+                {
+                    return;
+                }
+
+                var oldValue = _locationDetermined;
+                _locationDetermined = value;
+
+                // Update bindings, no broadcast
+                RaisePropertyChanged(LocationDeterminedPropertyName);
+                RaisePropertyChanged(DirectionDeterminedPropertyName);
+            }
+        }
+
+        /// <summary>
+        /// The <see cref="MotionDetermined" /> property's name.
+        /// </summary>
+        public const string MotionDeterminedPropertyName = "MotionDetermined";
+
+        private bool _motionDetermined = false;
+
+        /// <summary>
+        /// Gets the MotionDetermined property.
+        /// Changes to that property's value raise the PropertyChanged event. 
+        /// This property's value is broadcasted by the Messenger's default instance when it changes.
+        /// </summary>
+        public bool MotionDetermined
+        {
+            get
+            {
+                return _motionDetermined;
+            }
+
+            set
+            {
+                if (_motionDetermined == value)
+                {
+                    return;
+                }
+
+                var oldValue = _motionDetermined;
+                _motionDetermined = value;
+
+                // Update bindings, no broadcast
+                RaisePropertyChanged(MotionDeterminedPropertyName);
+                RaisePropertyChanged(DirectionDeterminedPropertyName);
+            }
+        }
+
+        /// <summary>
+        /// The <see cref="DirectionDetermined" /> property's name.
+        /// </summary>
+        public const string DirectionDeterminedPropertyName = "DirectionDetermined";
+
+        /// <summary>
+        /// Gets the DirectionDetermined property.
+        /// Changes to that property's value raise the PropertyChanged event. 
+        /// </summary>
+        public bool DirectionDetermined
+        {
+            get
+            {
+                return _locationDetermined && _motionDetermined;
+            }
+        }
+
+        /// <summary>
+        /// The <see cref="CurrentMatrix" /> property's name.
+        /// </summary>
+        public const string CurrentMatrixPropertyName = "CurrentMatrix";
+
+        private Matrix3D _currentMatrix = Matrix3D.Identity;
+
+        /// <summary>
+        /// Gets the CurrentMatrix property.
         /// TODO Update documentation:
         /// Changes to that property's value raise the PropertyChanged event. 
         /// This property's value is broadcasted by the Messenger's default instance when it changes.
         /// </summary>
-        public double CurrentRotationZ
+        public Matrix3D CurrentMatrix
         {
             get
             {
-                return _currentRotationZ;
+                return _currentMatrix;
             }
 
             set
             {
-                if (_currentRotationZ == value)
+                if (_currentMatrix == value)
                 {
                     return;
                 }
 
-                var oldValue = _currentRotationZ;
-                _currentRotationZ = value;
+                var oldValue = _currentMatrix;
+                _currentMatrix = value;
 
                 // Update bindings, no broadcast
-                RaisePropertyChanged(CurrentRotationZPropertyName);
+                RaisePropertyChanged(CurrentMatrixPropertyName);
             }
         }
 
+
         /// <summary>
         /// The <see cref="Feedback" /> property's name.
         /// </summary>
         }
 
         /// <summary>
-        /// The <see cref="Busy" /> property's name.
-        /// </summary>
-        public const string BusyPropertyName = "Busy";
-
-        private bool _busy = false;
-
-        /// <summary>
-        /// Gets the Busy property.
-        /// TODO Update documentation:
-        /// Changes to that property's value raise the PropertyChanged event. 
-        /// This property's value is broadcasted by the Messenger's default instance when it changes.
-        /// </summary>
-        public bool Busy
-        {
-            get
-            {
-                return _busy;
-            }
-
-            set
-            {
-                if (_busy == value)
-                {
-                    return;
-                }
-
-                var oldValue = _busy;
-                _busy = value;
-
-                // Update bindings, no broadcast
-                RaisePropertyChanged(BusyPropertyName);
-            }
-        }
-
-        /// <summary>
         /// The <see cref="LocationServiceSetting" /> property's name.
         /// </summary>
         public const string LocationServiceSettingPropertyName = "LocationServiceSetting";
                 _watcher.StatusChanged += new EventHandler<GeoPositionStatusChangedEventArgs>(_watcher_StatusChanged);
             }
 
-            if (Busy)
-            {
-                return;
-            }
-
             Feedback = AppResources.FeedbackStartingLabel;
 
-            Busy = true;
-            var msg = new AnimateArrowMessage() { Run = true, Indeterminate = true };
-            Messenger.Default.Send<AnimateArrowMessage>(msg);
             _watcher.Start();
         }
 
                     Distance = GeoDistanceCalculator.DistanceInKilometers(loc.Latitude, loc.Longitude, Makkah.Latitude, Makkah.Longitude);
 
                     var destZ = 360.0 - GeoDistanceCalculator.InitialBearing(loc.Latitude, loc.Longitude, Makkah.Latitude, Makkah.Longitude); // counter-clockwise
+                    OffsetFromNorthMatrix = RotateZTransform(destZ);
+                    LocationDetermined = true;
 
                     StopLocationSensor();
-                    var msg = new AnimateArrowMessage() { Run = true, Indeterminate = false, DestinationZ = destZ };
-                    Messenger.Default.Send<AnimateArrowMessage>(msg);
                     break;
             }
         }
                 return;
             }
 
-            Busy = false;
-            var msg = new AnimateArrowMessage() { Run = false };
-            Messenger.Default.Send<AnimateArrowMessage>(msg);
             _watcher.Stop();
         }
 
 
         private void CurrentValueChanged(MotionReading e)
         {
-            AttitudeReading ar = e.Attitude;
-            Feedback = String.Format("Pitch: {0}, Yaw: {1}, Roll: {2}", ar.Pitch, ar.Yaw, ar.Roll);
+            const double arrowWidth = 400.0;
+            const double arrowHeight = 500.0;
+            const double layoutWidth = 400.0;
+            const double layoutHeight = 500.0;
+
+            // Translate the image along the negative Z-axis such that it occupies 50% of the
+            // vertical field of view.
+            double fovY = Math.PI / 2.0;
+            double translationZ = -arrowHeight / Math.Tan(fovY / 2.0);
+
+            // You can create a 3D effect by creating a number of simple 
+            // tranformation Matrix3D matrixes and then multiply them together.
+            Matrix3D centerImageAtOrigin = TranslationTransform(
+                     -arrowWidth / 2.0,
+                     -arrowHeight / 2.0, 0);
+            Matrix3D invertYAxis = CreateScaleTransform(1.0, -1.0, 1.0);
+            Matrix3D translateAwayFromCamera = TranslationTransform(0, 0, translationZ);
+            Matrix3D perspective = PerspectiveTransformFovRH(fovY,
+                    layoutWidth / layoutHeight,                         // aspect ratio
+                    1.0,                                                // near plane
+                    1000.0);                                            // far plane
+            Matrix3D viewport = ViewportTransform(layoutWidth, layoutHeight);
+
+            Matrix3D m = centerImageAtOrigin * invertYAxis;
+            m = m * OffsetFromNorthMatrix;
+            m = m * XnaMatrixToMatrix3D(e.Attitude.RotationMatrix);
+            m = m * translateAwayFromCamera;
+            m = m * perspective;
+            m = m * viewport;
+
+            CurrentMatrix = m;
+            MotionDetermined = true;
+        }
+
+        private Matrix3D XnaMatrixToMatrix3D(Matrix matrix)
+        {
+            Matrix3D m = new Matrix3D();
+
+            m.M11 = matrix.M11; m.M12 = matrix.M12; m.M13 = matrix.M13; m.M14 = matrix.M14;
+            m.M21 = matrix.M21; m.M22 = matrix.M22; m.M23 = matrix.M23; m.M24 = matrix.M24;
+            m.M31 = matrix.M31; m.M32 = matrix.M32; m.M33 = matrix.M33; m.M34 = matrix.M34;
+            m.OffsetX = matrix.M41; m.OffsetY = matrix.M42; m.OffsetZ = matrix.M43; m.M44 = matrix.M44;
+
+            return m;
+        }
+
+        private Matrix3D CreateScaleTransform(double sx, double sy, double sz)
+        {
+            Matrix3D m = new Matrix3D();
+
+            m.M11 = sx; m.M12 = 0.0; m.M13 = 0.0; m.M14 = 0.0;
+            m.M21 = 0.0; m.M22 = sy; m.M23 = 0.0; m.M24 = 0.0;
+            m.M31 = 0.0; m.M32 = 0.0; m.M33 = sz; m.M34 = 0.0;
+            m.OffsetX = 0.0; m.OffsetY = 0.0; m.OffsetZ = 0.0; m.M44 = 1.0;
+
+            return m;
+        }
+
+        private Matrix3D TranslationTransform(double tx, double ty, double tz)
+        {
+            Matrix3D m = new Matrix3D();
+            m.M11 = 1.0; m.M12 = 0.0; m.M13 = 0.0; m.M14 = 0.0;
+            m.M21 = 0.0; m.M22 = 1.0; m.M23 = 0.0; m.M24 = 0.0;
+            m.M31 = 0.0; m.M32 = 0.0; m.M33 = 1.0; m.M34 = 0.0;
+            m.OffsetX = tx; m.OffsetY = ty; m.OffsetZ = tz; m.M44 = 1.0;
+            return m;
+        }
+
+        private Matrix3D RotateZTransform(double degree)
+        {
+            double theta = degree * Math.PI / 180.0;
+            double cos = Math.Cos(theta);
+            double sin = Math.Sin(theta);
+
+            Matrix3D m = new Matrix3D();
+            m.M11 = cos; m.M12 = sin; m.M13 = 0.0; m.M14 = 0.0;
+            m.M21 = -sin; m.M22 = cos; m.M23 = 0.0; m.M24 = 0.0;
+            m.M31 = 0.0; m.M32 = 0.0; m.M33 = 1.0; m.M34 = 0.0;
+            m.OffsetX = 0.0; m.OffsetY = 0.0; m.OffsetZ = 0.0; m.M44 = 1.0;
+            return m;
+        }
+
+        private Matrix3D PerspectiveTransformFovRH(double fieldOfViewY, double aspectRatio, double zNearPlane, double zFarPlane)
+        {
+            double height = 1.0 / Math.Tan(fieldOfViewY / 2.0);
+            double width = height / aspectRatio;
+            double d = zNearPlane - zFarPlane;
+
+            Matrix3D m = new Matrix3D();
+            m.M11 = width; m.M12 = 0; m.M13 = 0; m.M14 = 0;
+            m.M21 = 0; m.M22 = height; m.M23 = 0; m.M24 = 0;
+            m.M31 = 0; m.M32 = 0; m.M33 = zFarPlane / d; m.M34 = -1;
+            m.OffsetX = 0; m.OffsetY = 0; m.OffsetZ = zNearPlane * zFarPlane / d; m.M44 = 0;
+
+            return m;
+        }
+
+        private Matrix3D ViewportTransform(double width, double height)
+        {
+            Matrix3D m = new Matrix3D();
+
+            m.M11 = width / 2.0; m.M12 = 0.0; m.M13 = 0.0; m.M14 = 0.0;
+            m.M21 = 0.0; m.M22 = -height / 2.0; m.M23 = 0.0; m.M24 = 0.0;
+            m.M31 = 0.0; m.M32 = 0.0; m.M33 = 1.0; m.M34 = 0.0;
+            m.OffsetX = width / 2.0; m.OffsetY = height / 2.0; m.OffsetZ = 0.0; m.M44 = 1.0;
+
+            return m;
         }
 
         private void StopMotionSensor()

WhereIsMakkah/WhereIsMakkah.csproj

     </Compile>
     <Compile Include="Lang\LocalizedStrings.cs" />
     <Compile Include="Model\AppSettings.cs" />
-    <Compile Include="Util\AnimateArrowMessage.cs" />
+    <Compile Include="Util\BooleanToVisibilityConverter.cs" />
+    <Compile Include="Util\InverseBooleanConverter.cs" />
     <Compile Include="Util\SettingsChangedMessage.cs" />
     <Compile Include="Util\GoToPageMessage.cs" />
     <Compile Include="Util\GeoDistanceCalculator.cs" />