WPF 3D - 如何保存和加载摄像机视图?

时间:2009-11-25 16:41:20

标签: wpf 3d camera transform

我有一个WPF 3D场景,我可以使用3DTools library中的TrackballDecorator进行平移,旋转和缩放。我想保存相机设置(转换),并能够在下次重新启动应用程序时重新应用它们(以便恢复视图)。

我尝试保存Camera的每个值:

private void SaveCameraSettings()
{
  var d = Properties.Settings.Default;
  d.CameraPositionX = camera.Position.X;
  d.CameraPositionY = camera.Position.Y;
  ...
  d.Save();
}

这不起作用,我猜是因为这些设置没有根据应用到相机的转换进行更新(我总是得到在xaml中设置的初始值)。

我检查了Transformation3D类,但找不到任何设置其值的方法......

问题是我需要从PerspectiveCamera获取什么值,以便能够以最后一次关闭应用程序时的方式恢复它。摄像机设置为默认位置(在Xaml中),然后TrackBallDecorator将变换应用于此摄像机。如何保存此转换(要存储的值)?我怎样才能在以后重新应用它们?

3 个答案:

答案 0 :(得分:4)

这会有点长,所以请耐心等待......

第一,您需要修改3DTools库,以便将转化应用于TrackballDecorator,如下所示:

using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Media.Media3D;
using System.Windows.Input;

namespace _3DTools
{
  public class TrackballDecorator : Viewport3DDecorator
  {

    #region Private Members

    private Point m_PreviousPosition2D;
    private Vector3D m_PreviousPosition3D = new Vector3D(0, 0, 1);

    private Transform3DGroup m_Transform;
    private ScaleTransform3D m_Scale = new ScaleTransform3D();
    private AxisAngleRotation3D m_Rotation = new AxisAngleRotation3D();
    private TranslateTransform3D m_Translate = new TranslateTransform3D();

    private readonly Border m_EventSource;

    #endregion

    #region Constructor

    public TrackballDecorator()
    {
      TranslateScale = 10;
      ZoomScale = 1;
      RotateScale = 1;
      // the transform that will be applied to the viewport 3d's camera
      m_Transform = new Transform3DGroup();
      m_Transform.Children.Add(m_Scale);
      m_Transform.Children.Add(new RotateTransform3D(m_Rotation));
      m_Transform.Children.Add(m_Translate);

      // used so that we always get events while activity occurs within
      // the viewport3D
      m_EventSource = new Border { Background = Brushes.Transparent };

      PreViewportChildren.Add(m_EventSource);
    }

    #endregion

    #region Properties

    /// <summary>
    /// A transform to move the camera or scene to the trackball's
    /// current orientation and scale.
    /// </summary>
    public Transform3DGroup Transform
    {
      get { return m_Transform; }
      set
      {
        m_Transform = value;
        m_Scale = m_Transform.GetScaleTransform3D();
        m_Translate = m_Transform.GetTranslateTransform3D();
        m_Rotation = m_Transform.GetRotateTransform3D().Rotation as AxisAngleRotation3D;
        ApplyTransform();
      }
    }

    public double TranslateScale { get; set; }

    public double RotateScale { get; set; }

    public double ZoomScale { get; set; }

    #endregion

    #region Event Handling

    protected override void OnMouseDown(MouseButtonEventArgs e)
    {
      base.OnMouseDown(e);

      m_PreviousPosition2D = e.GetPosition(this);
      m_PreviousPosition3D = ProjectToTrackball(ActualWidth,
                                               ActualHeight,
                                               m_PreviousPosition2D);
      if (Mouse.Captured == null)
      {
        Mouse.Capture(this, CaptureMode.Element);
      }
    }

    protected override void OnMouseUp(MouseButtonEventArgs e)
    {
      base.OnMouseUp(e);

      if (IsMouseCaptured)
      {
        Mouse.Capture(this, CaptureMode.None);
      }
    }

    protected override void OnMouseMove(MouseEventArgs e)
    {
      base.OnMouseMove(e);

      if (IsMouseCaptured)
      {
        Point currentPosition = e.GetPosition(this);

        // avoid any zero axis conditions
        if (currentPosition == m_PreviousPosition2D) return;

        // Prefer tracking to zooming if both buttons are pressed.
        if (e.LeftButton == MouseButtonState.Pressed)
        {
          Track(currentPosition);
        }
        else if (e.RightButton == MouseButtonState.Pressed)
        {
          Zoom(currentPosition);
        }
        else if (e.MiddleButton == MouseButtonState.Pressed)
        {
          Translate(currentPosition);
        }

        m_PreviousPosition2D = currentPosition;

        ApplyTransform();
      }
    }

    private void ApplyTransform()
    {
      Viewport3D viewport3D = Viewport3D;
      if (viewport3D != null)
      {
        if (viewport3D.Camera != null)
        {
          if (viewport3D.Camera.IsFrozen)
          {
            viewport3D.Camera = viewport3D.Camera.Clone();
          }

          if (viewport3D.Camera.Transform != m_Transform)
          {
            viewport3D.Camera.Transform = m_Transform;
          }
        }
      }
    }

    #endregion Event Handling

    private void Track(Point currentPosition)
    {
      var currentPosition3D = ProjectToTrackball(ActualWidth, ActualHeight, currentPosition);

      var axis = Vector3D.CrossProduct(m_PreviousPosition3D, currentPosition3D);
      var angle = Vector3D.AngleBetween(m_PreviousPosition3D, currentPosition3D);

      // quaterion will throw if this happens - sometimes we can get 3D positions that
      // are very similar, so we avoid the throw by doing this check and just ignoring
      // the event 
      if (axis.Length == 0) return;

      var delta = new Quaternion(axis, -angle);

      // Get the current orientantion from the RotateTransform3D
      var r = m_Rotation;
      var q = new Quaternion(m_Rotation.Axis, m_Rotation.Angle);

      // Compose the delta with the previous orientation
      q *= delta;

      // Write the new orientation back to the Rotation3D
      m_Rotation.Axis = q.Axis;
      m_Rotation.Angle = q.Angle;

      m_PreviousPosition3D = currentPosition3D;
    }

    private static Vector3D ProjectToTrackball(double width, double height, Point point)
    {
      var x = point.X / (width / 2);    // Scale so bounds map to [0,0] - [2,2]
      var y = point.Y / (height / 2);

      x = x - 1;                           // Translate 0,0 to the center
      y = 1 - y;                           // Flip so +Y is up instead of down

      var z2 = 1 - x * x - y * y;       // z^2 = 1 - x^2 - y^2
      var z = z2 > 0 ? Math.Sqrt(z2) : 0;

      return new Vector3D(x, y, z);
    }

    private void Zoom(Point currentPosition)
    {
      var yDelta = currentPosition.Y - m_PreviousPosition2D.Y;

      var scale = Math.Exp(yDelta / 100) / ZoomScale;    // e^(yDelta/100) is fairly arbitrary.

      m_Scale.ScaleX *= scale;
      m_Scale.ScaleY *= scale;
      m_Scale.ScaleZ *= scale;
    }

    private void Translate(Point currentPosition)
    {
      // Calculate the panning vector from screen(the vector component of the Quaternion
      // the division of the X and Y components scales the vector to the mouse movement
      var qV = new Quaternion(((m_PreviousPosition2D.X - currentPosition.X) / TranslateScale),
      ((currentPosition.Y - m_PreviousPosition2D.Y) / TranslateScale), 0, 0);

      // Get the current orientantion from the RotateTransform3D
      var q = new Quaternion(m_Rotation.Axis, m_Rotation.Angle);
      var qC = q;
      qC.Conjugate();

      // Here we rotate our panning vector about the the rotaion axis of any current rotation transform
      // and then sum the new translation with any exisiting translation
      qV = q * qV * qC;
      m_Translate.OffsetX += qV.X;
      m_Translate.OffsetY += qV.Y;
      m_Translate.OffsetZ += qV.Z;
    }

  }

}

GetXXXTransform3D方法是扩展方法,定义如下:

public static ScaleTransform3D GetScaleTransform3D(this Transform3DGroup transform3DGroup)
{
  ScaleTransform3D scaleTransform3D = null;
  if (transform3DGroup != null)
  {
    foreach (var transform in transform3DGroup.Children)
    {
      scaleTransform3D = transform as ScaleTransform3D;
      if (scaleTransform3D != null) return scaleTransform3D;
    }
  }
  return scaleTransform3D;
}

public static RotateTransform3D GetRotateTransform3D(this Transform3DGroup transform3DGroup)
{
  RotateTransform3D rotateTransform3D = null;
  if (transform3DGroup != null)
  {
    foreach (var transform in transform3DGroup.Children)
    {
      rotateTransform3D = transform as RotateTransform3D;
      if (rotateTransform3D != null) return rotateTransform3D;
    }
  }
  return rotateTransform3D;
}

public static TranslateTransform3D GetTranslateTransform3D(this Transform3DGroup transform3DGroup)
{
  TranslateTransform3D translateTransform3D = null;
  if (transform3DGroup != null)
  {
    foreach (var transform in transform3DGroup.Children)
    {
      translateTransform3D = transform as TranslateTransform3D;
      if (translateTransform3D != null) return translateTransform3D;
    }
  }
  return translateTransform3D;
}

第二次,您需要向Transform申报PerspectiveCamera,如下所示:
(这个例子来自Sasha Barber的Elements3D项目,我曾用它测试过这个项目)

<Tools:TrackballDecorator x:Name="tbViewPort">

  <Viewport3D x:Name="vpFeeds">

    <Viewport3D.Camera>
      <PerspectiveCamera x:Name="camera" Position="-2,2,40" LookDirection="2,-2,-40" FieldOfView="90">
        <PerspectiveCamera.Transform>
          <Transform3DGroup />
        </PerspectiveCamera.Transform>
      </PerspectiveCamera>
    </Viewport3D.Camera>

    <ContainerUIElement3D x:Name="container" />

    <ModelVisual3D x:Name="model">
      <ModelVisual3D.Content>
        <DirectionalLight Color="White" Direction="-1,-1,-1" />
      </ModelVisual3D.Content>
    </ModelVisual3D>

  </Viewport3D>
</Tools:TrackballDecorator>

第3次,因为我们要将整个转换的每个部分存储在单独的值中,您需要在设置文件中创建相关属性,即CameraScaleX,{{ 1}},CameraScaleYCameraScaleZCameraTranslateXCameraTranslateYCameraTranslateZCameraRotateAxisXCameraRotateAxisY和{{1} }。所有类型都为CameraRotateAxisZ,并存储在用户范围内。

第4次,最后一步是使用以下代码将这些设置实际保存并加载到相机中:

CameraRotateAngle

希望我没有忘记任何事情。如果您需要更多帮助或者不理解某些内容,请不要犹豫; - )

答案 1 :(得分:0)

您需要相机视图矩阵数据和投影矩阵数据。视图矩阵将包含有关摄像机位置,旋转,比例和平移的数据,投影矩阵将包含视野,近平面,远平面和其他数据等内容。

很抱歉,由于我没有使用WPF,我无法帮助导出/导入该数据,但如果它使用as3的内置Matrix类中的任何内容,可能会暴露rawdata属性,这通常是as3 Vector。将矩阵16的值作为行有序浮点值公开。

答案 2 :(得分:0)

我相信你需要的是Position,LookDirection,UpDirection,FieldOfView,NearPlaneDistance,FarPlaneDistance。以上所有属性都定义了相机。