Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

3D globe rotation issues

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

like image 960
Mark Avatar asked Nov 21 '25 14:11

Mark


1 Answers

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.

like image 150
Julio Avatar answered Nov 24 '25 04:11

Julio



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!