basvk
basvk

Reputation: 4546

iPhone wheel (of fortune) rotation momentum

I'm having difficulties adding momentum to my spinning wheel.
I have this wheel (something like this) and I'm rotating it around it's center by using a single touch event.
No problems here, but when the touch (aka drag) ends; I want the wheel to keep it's momentum and ease out it's movement.

Anyone who can give me some pointers, it doesn't necessarily have to be in objective-c. AS3, javascript or JAVA will also be sufficient.

* UPDATE (code for rotating the wheel) *

    -(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
        rotation = _startAngle = atan2(384 - touchPoint.y, 512 - touchPoint.x);
    };

    -(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
        CGPoint touchPoint = [[touches anyObject] locationInView:self.view];
        rotation = atan2(384 - touchPoint.y, 512 - touchPoint.x);
        rotation = fmod(rotation - _startAngle, M_PI * 2.0f);
        [wheel setTransform:CGAffineTransformMakeRotation(_circleRotationOffset + rotation)];   
    };

    -(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
        _circleRotationOffset = fmod(_circleRotationOffset + rotation, M_PI * 2);
    };

Upvotes: 4

Views: 3338

Answers (3)

basvk
basvk

Reputation: 4546

I managed to get some nice results.
How I did it, should be tweaked more, but this is the basics:

    -(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
        CGPoint touchPoint = [[touches anyObject] locationInView:self.view];
        rotation = _startAngle = atan2(384 - touchPoint.y, 512 - touchPoint.x);
        [_history removeAllObjects];
    };

    -(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
        CGPoint touchPoint = [[touches anyObject] locationInView:self.view];
        [_history insertObject:[NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithDouble:CFAbsoluteTimeGetCurrent()], @"time", [NSValue valueWithCGPoint:touchPoint], @"point", [NSNumber numberWithFloat: _circleRotationOffset + rotation], @"rotation", nil] atIndex:0];
        if ([_history count] == 3) {
            [_history removeLastObject];
        }

        rotation = atan2(384 - touchPoint.y, 512 - touchPoint.x) - _startAngle;
        [circleImage setTransform:CGAffineTransformMakeRotation(_circleRotationOffset +         rotation)]; 
    };

    -(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {   
        CGPoint tp = [[touches anyObject] locationInView:self.view];
        _circleRotationOffset += rotation;

        NSDictionary *lo = [_history lastObject];
        CGPoint pp = [[lo objectForKey:@"point"] CGPointValue];
        double timeDif = CFAbsoluteTimeGetCurrent() - [[lo objectForKey:@"time"] doubleValue];
        float lastRotation = [[lo objectForKey:@"rotation"] floatValue];

        // Calculate strength
        float dist = sqrtf(((pp.x - tp.x) * (pp.x - tp.x)) + ((pp.y - tp.y) * (pp.y - tp.y)));
        float strength = MIN(1.0f, dist / 80.0f) * (timeDif / .025) * M_PI;

        float p = _circleRotationOffset;
        float dif = _circleRotationOffset - lastRotation;
        BOOL inc = dif > 0;
        if (dif > 3 || dif < -3) { // Some correction
            inc = !inc;
        }

        if (inc) {
            _circleRotationOffset += strength;  
        } else {
            _circleRotationOffset -= strength;
        }

        [circleImage.layer removeAllAnimations];
        CAKeyframeAnimation *animation = [CAKeyframeAnimation animationWithKeyPath:@"transform.rotation.z"];
        animation.duration = MAX(strength / 2.5, 1.0f);
        animation.cumulative = YES;
        animation.repeatCount = 1;
        animation.values = [NSArray arrayWithObjects:          
                    [NSNumber numberWithFloat:p], 
                    [NSNumber numberWithFloat: _circleRotationOffset], nil]; 
        animation.keyTimes = [NSArray arrayWithObjects:    
                      [NSNumber numberWithFloat:0], 
                      [NSNumber numberWithFloat:1.0], nil]; 
        animation.timingFunctions = [NSArray arrayWithObjects:
                             [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut], nil];
        animation.removedOnCompletion = YES;
        animation.delegate = self;
        animation.fillMode = kCAFillModeForwards;

        [circleImage.layer addAnimation:animation forKey:@"rotate"];
    };

Upvotes: 1

Sam Baumgarten
Sam Baumgarten

Reputation: 2249

If you are spinning the UIImageView with code like:
[UIView beginAnimations:@"rotateImage" context:nil];
[UIView setAnimationDuration:4.0];
wheelImageView.transform = CGAffineTransformMakeRotation(3.14159265*5);
[UIView commitAnimations];

You could use [UIView setAnimationCurve:UIViewAnimationCurveEaseOut];
This makes it so the animation will start off fast and start to slow down over time.

Click Here for More info on UIViewAnimationCurve

Upvotes: 1

Tommy
Tommy

Reputation: 100632

You want the momentum to reduce due to friction; friction is a force that's a function of velocity. So technically you've got a differential equation going on. That's not really worth investing too much thought into though, because the solution is probably more easily reached by hand waving.

So: store current angle and current angular velocity. n times a second (probably via an NSTimer or a CADisplayLink) add the angular velocity to the angle, then multiply the angular velocity by something to make it smaller — such as 0.995. Constants closer to 1.0 will make it take longer to slow down; if you go above 1.0 it'll obviously accelerate. This is effectively a form of Euler integration but, again, it's not worth worrying about.

It's possibly also worth putting a minimum cap on angular velocity so that if it drops below, say 0.01 radians/second then you snap it down to 0. That effectively modifies your model of friction slightly to jump from kinetic to static friction at an opportune moment, and acts as a floating point precision buffer zone.

To get initial velocity from a drag you can just work out the vector from the centre of the wheel to the start of the drag, rotate that vector by 90 degrees, do a dot product with that and the drag vector and scale according to distance from the centre.

Upvotes: 5

Related Questions