任意轴周围点的三维旋转

时间:2014-10-12 01:53:39

标签: c# wpf 3d rotation quaternions

对于我目前正在进行的项目,我正在尝试使用3维的二维地图。对于这个项目,我需要能够在三维空间中使用地图上的点(以二维定义)。

地图在原点处围绕矢量(-5,1,1)旋转。我无法将XY平面上的点旋转到三维的适当位置。

这就是我正在做的事情:

private Point3D MapXY2Screen3D
    (
    MapPoint mapPoint
    )
{
    // Width and Height of the map, both 600
    double Xmax = Map.ActualWidth;
    double Ymax = Map.ActualHeight;

    // Convert ESRI map coordinates to screen coordinates
    Point ScreenCoordinates = Map.MapToScreen(mapPoint);

    // Normalize the screen coordinates so they fall in the range -1..1
    ScreenCoordinates.X = ((2*ScreenCoordinates.X)/Xmax) - 1;
    ScreenCoordinates.Y = 1 - ((2*ScreenCoordinates.Y)/Ymax);

    // Create a Quaternion from the original location: Petzold 
    // Chapter 8 - Low-Level Quaternion Rotation
    var originQuaternion = 
        new Quaternion(ScreenCoordinates.X, ScreenCoordinates.Y, 0, 0);

    // Multiply rotation quaternion by origin by conjugate of rotation quaternion
    // to get the rotated point as a quaternion.
    var rotatedPoint = RotationQuaternion *
                       originQuaternion *
                       RotationQuaternionConjugate;

    // Return the X, Y, Z of the rotated quaternion as a 3D point
    return new Point3D(rotatedPoint.X, rotatedPoint.Y, rotatedPoint.Z);
}

我可以看到一些潜在的问题,这些问题可能会导致我所创造的点的位置偏离它们应该存在的位置。

首先,如果地图坐标的标准化不正确,旋转后该点将明显位于错误的位置。对我来说,这些公式是有意义的,从一些简单的调试,结果看起来是正确的。

其次,在我看的例子中,代码看起来有点不同:

    // Create a Quaternion from the original location: Petzold 
    // Chapter 8 - Low-Level Quaternion Rotation
    var originQuaternion = 
        new Quaternion(ScreenCoordinates.X, ScreenCoordinates.Y, 0, 0);

    *originQuaternion -= center;*

    // Multiply rotation quaternion by origin by conjugate of rotation quaternion
    // to get the rotated point as a quaternion.
    var rotatedPoint = RotationQuaternion *
                       originQuaternion *
                       RotationQuaternionConjugate;

    *rotatedPoint += center;*

center是指示旋转中心的四元数。我在原点周围旋转,所以我认为我不需要这样做。

以下是一些展示展示位置的图片:

在2d。兴趣点是NE部分的灰色方块。 2d image of program

在3d中。您仍然可以在地图平面上看到该点。 3d image of program

在3D中我正在创造。灰色方块上的预期红色圆圈。 enter image description here

完整代码

MainWindow.xaml.cs

using System;
using System.ComponentModel;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Media.Media3D;
using ESRI.ArcGIS.Client;
using ESRI.ArcGIS.Client.Geometry;
using ESRI.ArcGIS.Client.Symbols;
using Petzold.Media3D;

namespace Map3D
{
    /// <summary>
    ///     Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : INotifyPropertyChanged
    {
        private Quaternion _rotationQuaternion;
        private Quaternion _rotationQuaternionConjugate;
        private MapPoint _testMapPoint;

        #region PropertyChanged

        // General property changed event
        public event PropertyChangedEventHandler PropertyChanged;

        public void RaisePropertyChanged(string propertyName)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }

        #endregion

        public MainWindow()
        {
            InitializeComponent();
            DataContext = this;
            SetQuaternions();
        }

        public Quaternion RotationQuaternion
        {
            get { return _rotationQuaternion; }
            set
            {
                _rotationQuaternion = value;
                RaisePropertyChanged("RotationQuaternion");
            }
        }

        public Quaternion RotationQuaternionConjugate
        {
            get { return _rotationQuaternionConjugate; }
            set
            {
                _rotationQuaternionConjugate = value;
                RaisePropertyChanged("RotationQuaternionConjugate");
            }
        }

        public MapPoint TestMapPoint
        {
            get { return _testMapPoint; }
            set
            {
                _testMapPoint = value;
                RaisePropertyChanged("TestMapPoint");
            }
        }

        private void SetQuaternions()
        {
            var rotationAxis = new Vector3D(-5, 1, 1);
            rotationAxis.Normalize();

            RotationQuaternion = new Quaternion(rotationAxis, 65);
            RotationQuaternionConjugate = RotationQuaternion;
            RotationQuaternionConjugate.Conjugate();

            if (!RotationQuaternion.IsNormalized)
            {
                RotationQuaternion.Normalize();
            }
            if (!RotationQuaternionConjugate.IsNormalized)
            {
                RotationQuaternionConjugate.Normalize();
            }
        }

        private void Timeline_OnCompleted
            (
            object sender,
            EventArgs e
            )
        {
            Axes axes = new Axes();
            axes.Color = Colors.Red;
            axes.Extent = 1;
            axes.SetValue(Panel.ZIndexProperty, 100);
            MyModelVisual3D.Children.Add(axes);
        }

        private void Layer_OnInitialized
            (
            object sender,
            EventArgs e
            )
        {
            var graphic = new Graphic();
            var symbol = new SimpleMarkerSymbol
            {
                Style = SimpleMarkerSymbol.SimpleMarkerStyle.Circle,
                Size = 10,
                Color = new SolidColorBrush(Colors.DarkGray)
            };
            graphic.Symbol = symbol;

            var center = Map.Extent.GetCenter().Clone();

            MapPoint mapPoint = new MapPoint
            {
                X = 3*(Map.Extent.Width / 4) + Map.Extent.XMin,
                Y = 3*(Map.Extent.Height / 4) + Map.Extent.YMin
            };

            TestMapPoint = mapPoint;

            graphic.Geometry = mapPoint;

            var graphicsLayer = new GraphicsLayer
            {
                ID = "GraphicsLayer"
            };
            graphicsLayer.Graphics.Add(graphic);

            Map.Layers.Add(graphicsLayer);
        }

        private void PointButtonClick
            (
            object sender,
            RoutedEventArgs e
            )
        {
            GraphicsLayer graphicsLayer = 
                (GraphicsLayer) Map.Layers["GraphicsLayer"];
            MapPoint mapPoint = 
                (MapPoint) graphicsLayer.Graphics.First().Geometry;

            MyModelVisual3D.Children.Add(
                CreateSphere(0.025, Colors.Red, MapXY2Screen3D(mapPoint)));
        }

        private Visual3D CreateSphere
            (
            double radius,
            Color color,
            Point3D pt
            )
        {
            return new Sphere
            {
                Radius = radius,
                BackMaterial = new DiffuseMaterial
                {
                    Brush = new SolidColorBrush(color)
                },
                Center = pt,
            };
        }

        private Point3D MapXY2Screen3D
            (
            MapPoint mapPoint
            )
        {
            // Width and Height of the map, both 600
            double Xmax = Map.ActualWidth;
            double Ymax = Map.ActualHeight;

            // Convert ESRI map coordinates to screen coordinates
            Point ScreenCoordinates = Map.MapToScreen(mapPoint);

            // Normalize the screen coordinates so they fall in the range -1..1
            ScreenCoordinates.X = ((2*ScreenCoordinates.X)/Xmax) - 1;
            ScreenCoordinates.Y = 1 - ((2*ScreenCoordinates.Y)/Ymax);

            // Create a Quaternion from the original location: Petzold 
            // Chapter 8 - Low-Level Quaternion Rotation
            var originQuaternion = new Quaternion(
                ScreenCoordinates.X, ScreenCoordinates.Y, 0, 0);

            // Multiply rotation quaternion by origin by conjugate of rotation 
            // quaternion to get the rotated point as a quaternion.
            var rotatedPoint = RotationQuaternion *
                               originQuaternion *
                               RotationQuaternionConjugate;

            // Return the X, Y, Z of the rotated quaternion as a 3D point
            return new Point3D(rotatedPoint.X, rotatedPoint.Y, rotatedPoint.Z);
        }

        private void TiltButtonClick
            (
            object sender,
            RoutedEventArgs e
            )
        {
            var sb = Resources["MyStoryboard"] as Storyboard;
            sb.Begin();
        }
    }
}

MainWindow.xaml

<Window x:Class="Map3D.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:esri="http://schemas.esri.com/arcgis/client/2009"
    Title="MainWindow"
    Width="300" Height="300" mc:Ignorable="d" SizeToContent="WidthAndHeight">
    <Window.Resources>
        <Storyboard x:Key="MyStoryboard">
            <QuaternionAnimation Storyboard.TargetName="MyQuaternionRotation3D"
                                 Storyboard.TargetProperty="Quaternion"
                                 To="{Binding RotationQuaternion}"
                                 Completed="Timeline_OnCompleted"
                                 Duration="0:0:1" />
        </Storyboard>
    </Window.Resources>
    <Grid Width="800" Height="800">
        <Grid.RowDefinitions>
            <RowDefinition Height="*" />
            <RowDefinition Height="Auto" />
        </Grid.RowDefinitions>
        <Viewport3D Grid.Row="0">
            <Viewport3D.Camera>
                <PerspectiveCamera Position="0,0,4"
                                   LookDirection="0,0,-1"
                                   UpDirection="0,1,0" />
            </Viewport3D.Camera>
            <ModelVisual3D x:Name="MyModelVisual3D">
                <ModelVisual3D.Content>
                    <AmbientLight Color="White" />
                </ModelVisual3D.Content>
            </ModelVisual3D>
            <ModelVisual3D>
                <Viewport2DVisual3D>
                    <Viewport2DVisual3D.Transform>
                        <RotateTransform3D CenterX="0" CenterY="0" CenterZ="0">
                            <RotateTransform3D.Rotation>
                                <QuaternionRotation3D x:Name="MyQuaternionRotation3D"
                                                      Quaternion="0,0,0,0.5" />
                            </RotateTransform3D.Rotation>
                        </RotateTransform3D>
                    </Viewport2DVisual3D.Transform>
                    <Viewport2DVisual3D.Geometry>
                        <MeshGeometry3D Positions="-1 1 0, -1 -1 0, 1 -1 0, 1 1 0 "
                                        TextureCoordinates="0 0, 0 1, 1 1, 1 0"
                                        TriangleIndices="0 1 2 0 2 3" />
                    </Viewport2DVisual3D.Geometry>
                    <Viewport2DVisual3D.Material>
                        <DiffuseMaterial Viewport2DVisual3D.IsVisualHostMaterial="True" 
                                         Brush=White" />
                    </Viewport2DVisual3D.Material>

                    <!-- Map -->
                    <Grid x:Name="MapGrid" 
                          Width="600" Height="600" 
                          Panel.ZIndex="-1" Opacity="1" 
                          ClipToBounds="True">
                        <Border Background="Black">
                            <Border.Effect>
                                <BlurEffect Radius="15" />
                            </Border.Effect>
                        </Border>

                        <esri:Map x:Name="Map" Extent="5071751, 2615619, 6622505, 3609912">
                            <esri:Map.Layers>
                                <esri:ArcGISTiledMapServiceLayer
                                    Url="http://services.arcgisonline.com/ArcGIS/rest/services/World_Street_Map/MapServer"
                                    Initialized="Layer_OnInitialized"
                                    Visible="True" />
                            </esri:Map.Layers>
                        </esri:Map>
                    </Grid>
                </Viewport2DVisual3D>
            </ModelVisual3D>
        </Viewport3D>
        <UniformGrid Grid.Row="1"
                     Rows="1" Columns="2">
            <Button Margin="5" HorizontalAlignment="Center" VerticalAlignment="Center"     Click="PointButtonClick">Point</Button>
            <Button Margin="5" HorizontalAlignment="Center" VerticalAlignment="Center"     Click="TiltButtonClick">Tilt</Button>
        </UniformGrid>
    </Grid>
</Window>

1 个答案:

答案 0 :(得分:0)

在我上面发布的代码中,旋转四元数的共轭没有被正确计算或存储,所以我基本上是乘法:

rotated point = rotationQ * origin * rotationQ

这给出了反射,而不是旋转。为了解决这个问题,我将我的SetQuaternions方法更改为:

private void SetQuaternions()
{
    var rotationAxis = new Vector3D(-5f, 1f, 1f);

    RotationQuaternion = new Quaternion(rotationAxis, 65f);
    RotationQuaternion.Normalize();

    var q = RotationQuaternion;
    q.Conjugate();
    RotationQuaternionConjugate = q;

    if (!RotationQuaternion.IsNormalized)
    {
        RotationQuaternion.Normalize();
    }
    if (!RotationQuaternionConjugate.IsNormalized)
    {
        RotationQuaternionConjugate.Normalize();
    }
}

通过此更改,我的Sphere对象将在地图平面上,在感兴趣的位置创建。