objective-canimationrotationuigesturerecognizermomentum

Trying to get my head around simulating momentum / inertia witih a UIRotationGestureController


Okay, so I'm trying to make a 'Wheel of Fortune' type of effect with a wheel shape in iOS, where I can grab and spin a wheel. I can currently drag and spin the wheel around to my heart's content, but upon releasing my finger, it stops dead. I need to apply some momentum or inertia to it, to simulate the wheel spinning down naturally.

I've got the velocity calculation in place, so when I lift my finger up I NSLog out a velocity (between a minimum of 1 and a maximum of 100), which ranges from anywhere between 1 and over 1800 (at my hardest flick!), now I'm just trying to establish how I would go about converting that velocity into an actual rotation to apply to the object, and how I'd go about slowing it down over time.

My initial thoughts were something like: begin rotating full circles on a loop at the same speed as the velocity that was given, then on each subsequent rotation, slow the speed by some small percentage. This should give the effect that a harder spin goes faster and takes longer to slow down.

I'm no mathematician, so my approach may be wrong, but if anybody has any tips on how I could get this to work, at least in a basic state, I'd be really grateful. There's a really helpful answer here: iPhone add inertia/momentum physics to animate "wheel of fortune" like rotating control, but it's more theoretical and lacking in practical information on how exactly to apply the calculated velocity to the object, etc. I'm thinking I'll need some animation help here, too.

EDIT: I'm also going to need to work out if they were dragging the wheel clockwise or anti-clockwise.

Many thanks!


Solution

  • I have written something analogous for my program Bit, but my case I think is a bit more complex because I rotate in 3D: https://itunes.apple.com/ua/app/bit/id366236469?mt=8

    Basically what I do is I set up an NSTimer that calls some method regularly. I just take the direction and speed to create a rotation matrix (as I said, 3D is a bit nastier :P ), and I multiply the speed with some number smaller than 1 so it goes down. The reason for multiplying instead of subtracting is that you don't want the object to rotate twice as long if the spin from the user is twice as hard since that becomes annoying to wait on I find.

    As for figuring out which direction the wheel is spinning, just store that in the touchesEnded:withEvent: method where you have all the information. Since you say you already have the tracking working as long as the user has the finger down this should hopefully be obvious.

    What I have in 3D is something like:

    // MyView.h
    @interface MyView : UIView {
        NSTimer *animationTimer;
    }
    - (void) startAnimation;
    @end
    
    
    // MyAppDelegate.h
    @implementation MyAppDelegate
    
    - (void) applicationDidFinishLaunching:(UIApplication *)application {
        [myView startAnimation];
    }
    
    @end
    
    // MyView.m
    GLfloat rotationMomentum = 0;
    GLfloat rotationDeltaX  = 0.0f;
    GLfloat rotationDeltaY  = 0.0f;
    
    @implementation MyView
    - (void)startAnimation {
        animationTimer = [NSTimer scheduledTimerWithTimeInterval:(NSTimeInterval)((1.0 / 60.0) * animationFrameInterval) target:self selector:@selector(drawView:) userInfo:nil repeats:TRUE];
    }
    
    - (void) drawView:(id)sender {
        addRotationByDegree(rotationMomentum);
        rotationMomentum /= 1.05;
        if (rotationMomentum < 0.1)
            rotationMomentum = 0.1; // never stop rotating completely
        [renderer render];
    }
    
    - (void)touchesBegan:(NSSet*)touches withEvent:(UIEvent*)event
    {
    }
    
    - (void)touchesMoved:(NSSet*)touches withEvent:(UIEvent*)event
    {
        UITouch *aTouch = [touches anyObject];
        CGPoint loc = [aTouch locationInView:self];
        CGPoint prevloc = [aTouch previousLocationInView:self];
    
        rotationDeltaX = loc.x - prevloc.x;
        rotationDeltaY = loc.y - prevloc.y;
    
        GLfloat distance = sqrt(rotationDeltaX*rotationDeltaX+rotationDeltaY*rotationDeltaY)/4;
        rotationMomentum = distance;
        addRotationByDegree(distance);
    
        self->moved = TRUE;
    }
    
    - (void)touchesEnded:(NSSet*)touches withEvent:(UIEvent*)event
    {
    }
    
    - (void)touchesCancelled:(NSSet*)touches withEvent:(UIEvent*)event
    {
    }
    

    I've left out the addRotationByDegree function but what it does is that it uses the global variables rotationDeltaX and rotationDeltaY and applies a rotational matrix to an already stored matrix and then saves the result. In your example you probably want something much simpler, like (I'm assuming now that only movements in the X direction spin the wheel):

    - (void)touchesMoved:(NSSet*)touches withEvent:(UIEvent*)event
    {
        UITouch *aTouch = [touches anyObject];
        CGPoint loc = [aTouch locationInView:self];
        CGPoint prevloc = [aTouch previousLocationInView:self];
    
        GLfloat distance = loc.x - prevloc.x;
        rotationMomentum = distance;
        addRotationByDegree(distance);
    
        self->moved = TRUE;
    }
    
    void addRotationByDegree(distance) {
        angleOfWheel += distance; // probably need to divide the number with something reasonable here to have the spin be nicer
    }