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

3
我有一个WPF 3D场景,使用3DTools库中的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个回答

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;
}

第二步,您需要按照以下方式向您的PerspectiveCamera声明Transform:
(此示例取自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>

第三步,由于我们需要将整个变换的每个部分存储在单独的值中,您需要在设置文件中创建相关属性,即CameraScaleXCameraScaleYCameraScaleZCameraTranslateXCameraTranslateYCameraTranslateZCameraRotateAxisXCameraRotateAxisYCameraRotateAxisZCameraRotateAngle。所有这些属性都是double类型,并以用户范围存储。

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

private void SaveCameraSettings()
{
  var transform3DGroup = camera.Transform as Transform3DGroup;
  if (transform3DGroup != null)
  {
    foreach (var transform in transform3DGroup.Children)
    {
      var scale = transform as ScaleTransform3D;
      if (scale != null) SaveCameraSetting(scale);
      var rotate = transform as RotateTransform3D;
      if (rotate != null) SaveCameraSetting(rotate);
      var translate = transform as TranslateTransform3D;
      if (translate != null) SaveCameraSetting(translate);
    }
    Settings.Default.Save();
  }
}

private static void SaveCameraSetting(ScaleTransform3D transform)
{
  Properties.Settings.Default.CameraScaleX = transform.ScaleX;
  Properties.Settings.Default.CameraScaleY = transform.ScaleY;
  Properties.Settings.Default.CameraScaleZ = transform.ScaleZ;
}

private static void SaveCameraSetting(RotateTransform3D transform)
{
  var axisAngleRotation3D = transform.Rotation as AxisAngleRotation3D;
  if (axisAngleRotation3D != null)
  {
    Properties.Settings.Default.CameraRotateAxisX = axisAngleRotation3D.Axis.X;
    Properties.Settings.Default.CameraRotateAxisY = axisAngleRotation3D.Axis.Y;
    Properties.Settings.Default.CameraRotateAxisZ = axisAngleRotation3D.Axis.Z;
    Properties.Settings.Default.CameraRotateAngle = axisAngleRotation3D.Angle;
  }
}

private static void SaveCameraSetting(TranslateTransform3D transform)
{
  Properties.Settings.Default.CameraTranslateX = transform.OffsetX;
  Properties.Settings.Default.CameraTranslateY = transform.OffsetY;
  Properties.Settings.Default.CameraTranslateZ = transform.OffsetZ;
}

private void LoadCameraPosition()
{
  var d = Settings.Default;

  var transform3DGroup = new Transform3DGroup();

  var scaleTransform3D = new ScaleTransform3D(d.CameraScaleX, d.CameraScaleY, d.CameraScaleZ);
  var translateTransform3D = new TranslateTransform3D(d.CameraTranslateX, d.CameraTranslateY, d.CameraTranslateZ);
  var axisAngleRotation3D = new AxisAngleRotation3D(new Vector3D(d.CameraRotateAxisX, d.CameraRotateAxisY, d.CameraRotateAxisZ),
                                                    d.CameraRotateAngle);
  var rotateTransform3D = new RotateTransform3D(axisAngleRotation3D);

  transform3DGroup.Children.Add(scaleTransform3D);
  transform3DGroup.Children.Add(translateTransform3D);
  transform3DGroup.Children.Add(rotateTransform3D);

  tbViewPort.Transform = transform3DGroup;
}

希望我没有遗漏任何东西。如果您需要更多帮助或不理解某些内容,请随时提问;-)

谢谢!在Transform方法的第一步中,m_Transform没有.GetScaleTransform3D、.GetTranslateTransform3D或.GetRotateTransform3D。它们是扩展方法吗? - Michael
@Michael:是的,这些是我忘记包含的扩展方法。现在已经修复了,谢谢;-) - Julien Poulin
1
另一个值得注意的事情是,上述代码还将3dtools库添加了平移(使用中间鼠标按钮)功能。非常棒! - Michael
1
我对这个答案有一个小建议。在函数“Translate()”的第1步中,您可以将其更改为:“m_Translate.OffsetX += qV.X * m_scale.ScaleX;”,同样适用于Y和Z,以便始终正确地翻译相应的数量。 - Leon Bohmann

0

我相信你所需要的是:位置(Position)、朝向(LookDirection)、上方向(UpDirection)、视场(FieldOfView)、近平面距离(NearPlaneDistance)、远平面距离(FarPlaneDistance)。以上所有的属性都定义了相机。


0

你需要摄像机视图矩阵数据和投影矩阵数据。视图矩阵将包含有关相机位置、旋转、缩放和平移的数据,而投影矩阵将包含诸如视场、近平面、远平面和其他数据。

很抱歉我无法帮助导出/导入该数据,因为我没有使用过WPF,但如果它使用与as3内置矩阵类有关的任何内容,则可能会公开rawdata属性,这通常是一个as3 Vector。对象,矩阵的16个值作为行排序浮点值公开。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接