3D Sphere Rotation with Interia in WPF

Started by
2 comments, last by Kram 14 years, 5 months ago
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 inertia, 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
Advertisement
Ok, I have tracked it down!

When the user moves the sphere with their mouse, I GET the current mouse position, and the previous position, and work out the angle/axis for rotation. And like I said, that is working.

However, when the inertia kicks in, Im still using that same logic, so when the original position that the user clicks reaches the very outside of the sphere, the X and Y Axis of my calculations are all zero! Hence no rotation, and hence the bugs.

Now, what I do not really know, is, using the current maths and logic, how can I continue the spinning around the current axis and angle without getting the current and last positions? I just want the sphere to spin and slowly come to a halt...

Thanks for any help you can give...
I think I might rephrase my question now that it has changed a bit...

I have a Delta method that gets fired by my InertiaProcessor. The processor has a DesiredDeceleration property which I have set to 0.0001, this identifies how fast the inertia will stop, and hence how often the Delta method gets called.

The Delta method needs to rotate the Sphere.

This is the signature of the method:

private void Inertia_OnManipulationDelta(object sender, Affine2DOperationDeltaEventArgs e)


The goal of this method is to change the class field member: "_rotation" which has Axis(Vector3D) and Angle(double) properties.

The Affine2DOperationDeltaEventArgs class has the following properties (from the API doco):

AngularVelocity Gets the current rate of rotational change, in clockwise degrees per millisecond.

CumulativeExpansion Gets the change in the average radius, since the manipulation started.

CumulativeRotation Gets the amount of rotational change, in degrees clockwise, since the manipulation started.

CumulativeScale Gets the amount of scale change, as a percentage, since the manipulation started.

CumulativeTranslation Gets the amount of translational change since the manipulation started.

Delta Gets the amount of translational change since the last event.

ExpansionDelta Gets the change in the average radius since the last event.

ExpansionVelocity Gets the current rate of scale change, in device-independent units per millisecond.

ManipulationOrigin Gets the new coordinates of the composite position of the manipulation.

RotationDelta Gets the amount of rotational change, in degrees clockwise, since the last event.

ScaleDelta Gets the amount of scale change as a multiplier of the previous scale.

Velocity Gets the current rate of translational change, in device-independent units per millisecond.

So as you can see, Ive got the building blocks available to me, I just dont know how to apply them to rotate a sphere...

Currently, when I just rotate the sphere with the mouse (i.e. no inertia) Im using _current and _previous mouse positions, and mapping them to spots on the sphere using a little helper method, but I cannot get that to work when the points reach the back of the sphere!

Any ideas would be greatly appreciated!

Thanks
Mark
Sorry to bump but I'm still stuck on this one...

This topic is closed to new replies.

Advertisement