views:

729

answers:

2

Hi all,

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

A: 

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.

Joseph
Actually I have just tracked down the issue with the jumping, which it actually the wrong word to use. It doesnt jump it just speeds up and stops rotating around the X and Y axis. So it gives a crap effect. Whats happening is that when I map the last position to the shpere and that positions gets to the BACK of the sphere the X and Y axis calcuations Zero out...
Mark
@Mark It was worth a shot!
Joseph
+1  A: 

Hello,

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.

Julio
hey thanks for the help, honestly Im for changing around how it works... I only implemented the trackball approach because I saw it on a website.Basically, When the user flicks their mouse/finger, yeah, I get 2D operations that I must convert into 3D space, I have the "Velocity" and "ManipulationOrigin" (and other) properties availble to me, I just dont know how to apply them...
Mark
I downloaded your project and edited my answer. Hope this helps :)May you describe a little bit more what you mean by "jumping"?
Julio
That does help a lot actually! You are spot on with your diagnosis. The only issue is that I dont have a 3D inertia processor available to me. Can you please help me get this working with what Ive got? I would soooo hugely appreciate it... this stuff is doing my head in!
Mark
With the piece of code I posted in my last edit you should not need any inertia3D. This is far from perfect be cause the projection now forbids any full rotation in one move... So the movement may be too slow for your taste.
Julio
One last edit. I think you should take some time and learn 3D.
Julio
Agreed, I should, and I will, its just that time pressures are really restricting me here. I do apologise for simply asking for an answer, its not normally how i work :)However, when I replaced the method, it only rotates around the Z axis, and there is no actual inertia... (i.e. when I stop the mouse movement the globe does not keep spinning until a stop, is this the expected behaviour at your end?
Mark
It only rotates around the Z axis probably because _rotation is a AxisAngleRotation3D. You need a quaternion to be able to rotate fully (as I already said, you need 3 euler angles but you're currently overwriting your rotation with only one axis and angle). For the inertia, with my last bit of code you should see at least a little bit of inertia :) I have some ideas for a new ProjectToTrackball method, will try to find some time tomorrow.
Julio
Thats ok, thats a heap for your help I really appreciate it!
Mark
On new edit :)I think i'm done now. Hope you can manage to have something working !
Julio
Thanks so much for the help, I really do appreciate it so much
Mark