wpf - 3D 地球仪旋转问题

标签 wpf 3d rotation transformation pixelsense

当用户将鼠标/手指移到球体上时,我试图让 3D 球体旋转。

我可以让它毫无问题地旋转,但是当我尝试使用 Surface SDK 中的 Affine2DInertiaProcessor 为球体添加惯性时,当我快速轻弹球体时,我会遇到跳跃问题,我不知道为什么...

这是我的初始化代码:

    private void InitializeManipulationProcessor()
    {
        manipulationProcessor = new Affine2DManipulationProcessor(
            Affine2DManipulations.Rotate | 
            Affine2DManipulations.TranslateX | 
            Affine2DManipulations.TranslateY,
            _eventSource);


        inertiaProcessor = new Affine2DInertiaProcessor();
        inertiaProcessor.Affine2DInertiaDelta += Inertia_OnManipulationDelta;
        inertiaProcessor.Affine2DInertiaCompleted += InertiaProcessor_Affine2DInertiaCompleted;

        manipulationProcessor.Affine2DManipulationStarted += OnManipulationStarted;
        manipulationProcessor.Affine2DManipulationDelta += Manipulation_OnManipulationDelta;
        manipulationProcessor.Affine2DManipulationCompleted += OnManipulationCompleted;
}

当用户移动手指时,以下是旋转球体的代码:

private void Manipulation_OnManipulationDelta(object sender, Affine2DOperationDeltaEventArgs e)
    {
        Point currentPosition = e.ManipulationOrigin;
        // avoid any zero axis conditions
        if (currentPosition == _previousPosition2D)
            return;

        Track(currentPosition);

        _previousPosition2D = currentPosition;
    }

当用户停止移动手指时,这会启动 ineria:

private void OnManipulationCompleted(object sender, Affine2DOperationCompletedEventArgs e)
{
    inertiaProcessor.InitialOrigin = e.ManipulationOrigin;
    inertiaProcessor.InitialVelocity = e.Velocity;
    inertiaProcessor.DesiredDeceleration = 0.0001;
    inertiaProcessor.Begin();
}

旋转的魔力发生在下面的Track方法中:

    private void Track(Point currentPosition)
    {
        Vector3D currentPosition3D = ProjectToTrackball(currentPosition);

        Vector3D axis = Vector3D.CrossProduct(_previousPosition3D, currentPosition3D);
        double angle = Vector3D.AngleBetween(_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;

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

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

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

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

        _previousPosition3D = currentPosition3D;
    }

_rotation 变量是用于 3d 网格上的 RotateTransform3DAxisAngleRotation3D 类。

我知道这是一个特殊情况,但我有一种感觉,这是一个计算问题,我真的不知道如何调试它。

还有一件事,一件非常有趣的事情是,如果我慢慢地轻弹地球仪,我不会有任何跳跃,而且非常平滑!所以这肯定与大量计算有关,或者只是一些错误......

如果您擅长 3D 旋转并且真正相信您可以提供帮助,那么如果您需要更好的格式来使用,我将很乐意将此项目打包为 ZIP 并将其发送给您

感谢您提供的任何帮助,我非常感谢您的帮助!

标记

最佳答案

我没有明确的答案,但看看你的代码,很多事情对我来说似乎很奇怪。

首先,ProjectToTrackball 到底是做什么的? 由于您使用 2D 惯性,我假设它将 2D 点(在屏幕空间中)投影到球体上并返回 3D 点,对吗?那么当 2D 点位于屏幕上的球体之外时到底会发生什么?返回什么点?当您用手指在球体上开始移动并且 2D 惯性使移动移出球体时会发生什么?

现在介绍一下在 Track 方法中处理旋转的方式。我对四元数了解不多,但我确定的是,如果你想对 3D 旋转进行建模,你需要三个轴和 3 个角度(欧拉角)。在每一步中,您都将仅用一个轴和一个角度覆盖四元数。如果您仅向一个方向移动手指,则此方法有效。如果您在移动过程中改变方向,这将不起作用。

顺便说一句,我不明白为什么你在 delta 四元数中直接使用“-angle”而不是“angle”,但我想如果那里有错误,你会立即注意到它;)

编辑:我下载了你的.rar。

我研究了 ProjectToTrackball 的工作原理(在 SurfaceTrackballDecorator.cs 中),现在对正在发生的事情有了一个大概的了解。

首先,你的球体应该与整个屏幕匹配(这意味着即使你的屏幕不是正方形,它也应该接触屏幕的四个边),否则移动将无法正常运行。如果没有,您应该能够在球体和屏幕边缘之间的空间中旋转球体,我猜这不是所需的效果。

那么当惯性发挥作用时会发生的情况是,运动将在2维中继续,而不是在3D中,就好像你的手指一直在移动(慢慢地减慢速度。)

当运动到达球体边缘时,将 2D 点投影到 3D 球体上的魔力可以使球体旋转得非常快。因此,即使您的手指没有到达球体边缘,惯性 2D 也可以实现这一点。

现在有趣的是,一旦 2D 点不再投影在球体上(当穿过球体边缘时),运动就会残酷地停止。因为所有 2D 点现在都投影到 (z=0) 平面上。

我不知道这是否是你所说的“跳跃球体”的意思:)

现在要解决这个问题,您需要某种 3D 惯性来减慢 3D 旋转,而不是 2D 点移动。

我以前的观点仍然有效。希望这会有所帮助。

编辑:睡不着:)

你能试试这段代码吗?

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

  x = x - 1;                           // Translate 0,0 to the center
  y = 1 - y;                           // Flip so +Y is up instead of down
  double alpha = 1.0 / Math.Sqrt(x*x + y*y + 1);
  return new Vector3D(x*alpha, y*alpha, alpha);
}

我不保证任何东西,但这应该更加平滑(也许太多)并且球体边缘不再有不连续性......

唯一角度的问题仍然困扰着我......

编辑:新的:

  private Vector3D ProjectToTrackball(Point point)

  {
    // IMPORTANT NOTE: result should always be normalized

    double x = point.X / (ActualWidth / 2);    // Scale so bounds map to [0,0] - [2,2]

    double y = point.Y / (ActualHeight / 2);



    x = x - 1;                           // Translate 0,0 to the center

    y = 1 - y;                           // Flip so +Y is up instead of down



    double z2 = 1 - x * x - y * y;       // z^2 = 1 - x^2 - y^2
    double z =  0;

    if(z2 > 0)
      z2 = Math.Sqrt(z2); // Ok no need to normalize.
    else
    {
      // I will get rid of the discontinuity with a little trick:
      // I construct an imaginary point below the sphere.
      double length = Math.Sqrt(x * x + y * y);
      x = x / length;
      y = y / length;
      z = 1 - length;
      // Now I normalize:
      length = Math.Sqrt(x * x + y * y + z * z);
      x = x / length;
      y = y / length;
      z = z / length;
    }

    return new Vector3D(x, y, z);

  }

现在,当手指在球体内部移动时,它的行为应该像以前一样。穿过球体边缘时不再出现不连续性。移动速度应该很快减慢。

我在第二次编辑中犯了一个错误:跳跃可能是由于当 2D 点不再投影到球体上时,ProjectToTrackball 返回一个非标准化向量。所以在那之后一切都变得疯狂了。

注意:您应该拿起一本 OpenGL 书籍并学习 3D。

2009 年 11 月 18 日的新编辑:

关于唯一角度的问题,我认为这就是导致“它只绕Z轴旋转”问题的原因。

首先将 _rotation 更改为四元数。在将网格与 _rotation 相乘的方法中,必须更改一些代码,但这应该不会太困难。

然后你可以尝试这个新的 Track 方法:

private void Track(Point currentPosition)
{
    Vector3D currentPosition3D = ProjectToTrackball(currentPosition);

    Vector3D axis = Vector3D.CrossProduct(_previousPosition3D, currentPosition3D);
    double angle = Vector3D.AngleBetween(_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;

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

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

    _previousPosition3D = currentPosition3D;
}

对于惯性,我放弃了......你需要某种 3D 旋转惯性。

关于wpf - 3D 地球仪旋转问题,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/1665375/

相关文章:

c# - BindableBase.SetProperty不更新UI

c# - WPF - 在没有设计器的情况下创建实体布局

matlab - 如何强调球面上的某些区域

3d - SketchUp 导出带有纹理的对象 - 如何

automation - 苹果脚本旋转显示

c++ - 围绕与原点不同的点旋转

c# - unity 在最小和最大距离之间旋转

wpf - 扳机未触发

.net - 从 WPF 应用程序显示 "Select Users and Groups"对话框?

javascript - 3D 图形库中的树状图