3D 地球仪旋转问题

发布于 2024-08-09 13:37:28 字数 3185 浏览 5 评论 0原文

当用户将鼠标/手指移到球体上时,我试图让 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 并发送如果您需要更好的格式来使用,请告诉您

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

标记

Im trying to get my 3D sphere to rotate when a user moves their mouse/finger over the sphere.

I can get it to rotate no problems, but when I try to add inertia to the sphere using the Affine2DInertiaProcessor in the Surface SDK, I get jumping issues when I quickly flick the sphere, and I dont know why...

Here is my initialisation code:

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

When a user moves their finger, here is the code to rotate the sphere:

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

This starts the ineria, when the user stops moving their finger:

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

The magic of the rotation, happens in the Track method below:

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

The _rotation var is the AxisAngleRotation3D class used for the RotateTransform3D on the 3d mesh.

I know this is a specialty case, but I have a feeling that it is a calculation issue, and I really have no idea how to debug this.

One more thing, a very interesting thing to note is that if I flick the globe slowly I do NOT get any jumping and it is very smooth! So it must be something to do with either large calculations, or just some bug...

If you are good at 3D rotation and truly believe that you can help, then I will be happy to package up this project into a ZIP and send it to you if you need a better format to work with

Thanks for any help you can give, i really appreciate the help!

Mark

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

洛阳烟雨空心柳 2024-08-16 13:37:28

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

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

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

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

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

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

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

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

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

现在有趣的是,一旦 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 相乘的方法中,必须更改一些代码,但这应该不会太困难。

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

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旋转惯性。

I do not have a clear answer but looking at your your code bits many things seem strange to me.

First of all, what does ProjectToTrackball exactly do?
Since you use a 2D inertia, I assume it projects a 2D point (in the screen space) onto the sphere and returns a 3D point, right? So what exactly happens when the 2D point is outside the sphere on the screen? What point is returned? What happens when you start a move with your finger on the sphere and the inertia 2D makes the move go out of the sphere?

Now about the way you handle rotation in your Track method. I don't know much about quaternions, but what I know for sure is that if you want to modelize a 3D rotation, you need three axis and 3 angles (Euler's angles). At each step you are overwriting you quaternion with only one axis and one angle. This works if you move your finger in only one direction. This can't work if you change directions during the movement.

On a side note I don't understand why you use "-angle" and not "angle" directly in your delta quaternion but I guess you would have noticed it imediately if there was a bug there ;)

EDIT: I downloaded your .rar.

I looked at how ProjectToTrackball worked (in SurfaceTrackballDecorator.cs) and now have a fair idea of what is happening.

First of all, your sphere should match the whole screen (meaning it should touch the four sides of the screen even if your screen is not square) or else the movement won't behave normally. If not you should be able to rotate the sphere in the spaces between the sphere and the screen edges, which is not the desired effect I guess.

Then what will happen when the inertia does its job is that the movement will continue in 2 dimensions, not in 3D, as if your finger kept moving (slowly slowing down.)

When the movement hits the sphere edges, the magic of projecting a 2D point onto a 3D sphere can make the sphere spin really fast. So even if your finger didn't go to the sphere edges, the inertia2D can make this happen.

Now the fun is that as soon as the 2D point does not project any more on the sphere (when crossing the sphere edge), then the movement brutally stops. Because all the 2D points now project onto the (z=0) plane.

I don't know if this is what you meant by "jumping sphere" :)

Now to solve this you need to have some kind of inertia 3D that slows down a 3D rotation, not a 2D point movement.

My former points are still valid. Hope this helps.

EDIT: can't sleep :)

Could you try this piece of code ?

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

I do not guaranty anything but this should be much smoother (maybe too much) and there is no more discontinuities at the sphere edges...

The problem with the only angle still bothers me though...

EDIT: New one:

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

  }

Now it should behave just like before for movement where your finger is inside the sphere. No more discontinuity when crossing the sphere edge. Movement should slow down quickly.

I made a mistake in my second edit: jumps probably came from the fact that when the 2D point does not project onto the sphere anymore, ProjectToTrackball returns a NON normalized vector. So everything went nuts after that.

NOTE: you should pick up an OpenGL book and learn 3D.

New EDIT on 11/18/2009:

About the problem of the only angle, I assume it's what causes the 'it only rotates around the Z axis' problem.

First change _rotation to a quaternion. Some code will have to be changed in your method where you multiply the mesh with _rotation but it should not be too diffcult.

Then you can try this new Track method:

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

For Inertia I gave up... You need some kind of 3D rotating inertia.

童话 2024-08-16 13:37:28

我在这里进行了一次巨大的尝试,但从您提供的代码来看,如果 Manipulation_OnManipulationDelta 和 OnManipulationCompleted 事件之间的 ManipulationOrigin 属性显着不同,会发生什么?

在我看来,如果用户快速弹动手指,可能会导致事件之间的值显着不同,这可能是您遇到的“跳跃”症状的原因。

I'm taking a giant stab in the dark here, but judging from the code you put up, what would happen if the ManipulationOrigin property was dramatically different between the Manipulation_OnManipulationDelta and the OnManipulationCompleted events?

It looks to me that if a user flicked their finger very quickly it might cause the value to be significantly different between events, which might account for the "jumping" symptom you're experiencing.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文