Skip to content

Instantly share code, notes, and snippets.

@millenomi
Created April 2, 2010 17:43
Show Gist options
  • Save millenomi/353435 to your computer and use it in GitHub Desktop.
Save millenomi/353435 to your computer and use it in GitHub Desktop.
//
// MvrInertia.h
// Mover-iPad
//
// Created by ∞ on 30/03/10.
// Copyright 2010 __MyCompanyName__. All rights reserved.
//
#import <UIKit/UIKit.h>
@protocol MvrAnimationStep;
typedef struct {
BOOL shouldAnimate;
CGPoint destination;
NSTimeInterval duration;
} MvrAnimationStep;
@interface MvrInertia : NSObject {
NSDate* motionStartTime;
NSTimer* speedTrap;
CGPoint lastMotion;
CGPoint lastCheckpointLocation;
NSTimeInterval lastCheckpointTime;
CGFloat minimumSpeedToTriggerSlide, attrition;
}
- (void) addMotionToPoint:(CGPoint) p;
- (MvrAnimationStep) endMotionWithPoint:(CGPoint) p;
// measured in point units per 100 ms.
// Defaults to 7 units per 100 ms.
@property CGFloat minimumSpeedToTriggerSlide;
// this is a slowdown factor -- how much speed the slide loses for each unit of "travel"
// a factor of 0.5 means that the inertial slide will cause the point to travel X, then 0.5*X,
// then 0.5*0.5*X and so on until it converges to a value.
// Defaults to 0.5. MUST be a reasonable value (in range 0.0-1.0, but not too near 1.0 nor too near 0.0).
@property CGFloat attrition;
@end
//
// MvrInertia.m
// Mover-iPad
//
// Created by ∞ on 30/03/10.
// Copyright 2010 __MyCompanyName__. All rights reserved.
//
#import "MvrInertia.h"
#define MvrILog(x, ...) NSLog(@"%@, %s: " x, self, __func__, ## __VA_ARGS__)
static inline BOOL MvrAbsoluteWithinRange(CGPoint vector, CGFloat rangeAbs) {
return
ABS(vector.x) < rangeAbs && ABS(vector.y) < rangeAbs;
}
static inline NSString* MvrNSStringFromAnimationStep(MvrAnimationStep s) {
if (s.shouldAnimate)
return [NSString stringWithFormat:@"<Animation step: animation %f s long to %@", (double) s.duration, NSStringFromCGPoint(s.destination)];
else
return @"<Animation step: none>";
}
@interface MvrInertia ()
- (NSTimeInterval) timestamp;
@end
static MvrAnimationStep MvrMakeAnimationStep(BOOL shouldAnimate, CGPoint destination, NSTimeInterval duration) {
MvrAnimationStep s;
s.shouldAnimate = shouldAnimate;
s.destination = destination;
s.duration = duration;
return s;
}
@implementation MvrInertia
- (id) init
{
self = [super init];
if (self != nil) {
self.attrition = 0.5;
self.minimumSpeedToTriggerSlide = 7.0;
}
return self;
}
- (void) dealloc
{
[speedTrap invalidate];
[speedTrap release];
[motionStartTime release];
[super dealloc];
}
@synthesize minimumSpeedToTriggerSlide, attrition;
- (void) addMotionToPoint:(CGPoint) p;
{
if (!speedTrap) {
MvrILog(@"Setting up the speed trap.");
speedTrap = [[NSTimer scheduledTimerWithTimeInterval:0.2 target:self selector:@selector(recordSpeed:) userInfo:nil repeats:YES] retain];
motionStartTime = [NSDate new];
lastCheckpointLocation = p;
lastCheckpointTime = 0.0;
}
MvrILog(@"Recorded motion to point: %@", NSStringFromCGPoint(p));
lastMotion = p;
}
- (NSTimeInterval) timestamp;
{
NSTimeInterval t = -[motionStartTime timeIntervalSinceNow];
MvrILog(@"%f", t);
return t;
}
- (void) recordSpeed:(NSTimer*) t;
{
lastCheckpointLocation = lastMotion;
lastCheckpointTime = [self timestamp];
MvrILog(@"Checkpointed speed");
}
- (MvrAnimationStep) endMotionWithPoint:(CGPoint) here;
{
MvrILog(@"Final point for movement is %@", NSStringFromCGPoint(here));
NSTimeInterval movementTime = [self timestamp] - lastCheckpointTime;
MvrILog(@"Last movement lasted: %f", movementTime);
CGPoint movementVector;
movementVector.x = here.x - lastCheckpointLocation.x;
movementVector.y = here.y - lastCheckpointLocation.y;
MvrILog(@"Movement delta vector is %@", NSStringFromCGPoint(movementVector));
CGPoint speedPointsPer100MS;
speedPointsPer100MS.x = (movementVector.x / movementTime) * 0.1;
speedPointsPer100MS.y = (movementVector.y / movementTime) * 0.1;
MvrILog(@"Speed vector is %@", NSStringFromCGPoint(speedPointsPer100MS));
BOOL performInertialSlide = !MvrAbsoluteWithinRange(speedPointsPer100MS, self.minimumSpeedToTriggerSlide);
MvrILog(@"Enough to slide? %d", performInertialSlide);
MvrAnimationStep result;
if (!performInertialSlide)
result = MvrMakeAnimationStep(NO, CGPointZero, 0);
else {
CGFloat dampening = self.attrition;
CGPoint delta = movementVector;
int timeScale = 1;
while (!MvrAbsoluteWithinRange(movementVector, 5.0)) {
movementVector.x *= dampening;
movementVector.y *= dampening;
delta.x += movementVector.x;
delta.y += movementVector.y;
timeScale++;
}
here.x += delta.x;
here.y += delta.y;
result = MvrMakeAnimationStep(YES, here, movementTime * timeScale);
}
[speedTrap invalidate];
[speedTrap release]; speedTrap = nil;
MvrILog(@"Result: %@", MvrNSStringFromAnimationStep(result));
return result;
}
@end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment