diff --git a/RBBAnimation.xcodeproj/project.pbxproj b/RBBAnimation.xcodeproj/project.pbxproj index 79c2a47..bed3132 100644 --- a/RBBAnimation.xcodeproj/project.pbxproj +++ b/RBBAnimation.xcodeproj/project.pbxproj @@ -7,6 +7,12 @@ objects = { /* Begin PBXBuildFile section */ + 5409356419052226007565F7 /* RBBDampedHarmonicOscillaton.m in Sources */ = {isa = PBXBuildFile; fileRef = 5466A9B418F6C2260090E3E6 /* RBBDampedHarmonicOscillaton.m */; }; + 540935651905223A007565F7 /* RBBDampedHarmonicOscillaton.h in Headers */ = {isa = PBXBuildFile; fileRef = 5466A9B318F6C2260090E3E6 /* RBBDampedHarmonicOscillaton.h */; }; + 540935661905223D007565F7 /* RBBRubberbandAnimation.h in Headers */ = {isa = PBXBuildFile; fileRef = 545FEFD418F177B000900B78 /* RBBRubberbandAnimation.h */; }; + 5409356719052241007565F7 /* RBBRubberbandAnimation.m in Sources */ = {isa = PBXBuildFile; fileRef = 545FEFD518F177B000900B78 /* RBBRubberbandAnimation.m */; }; + 540935681905224C007565F7 /* RBBDampedHarmonicOscillatonSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = 549ECA8518F7010400D968C7 /* RBBDampedHarmonicOscillatonSpec.m */; }; + 5409356919052251007565F7 /* RBBRubberbandAnimationSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = 549ECA8718F7028E00D968C7 /* RBBRubberbandAnimationSpec.m */; }; 540F65A318F81DC9005A6BB8 /* Pods-RBBAnimationTest.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 540F65A218F81DC9005A6BB8 /* Pods-RBBAnimationTest.xcconfig */; }; 540F65A718F82347005A6BB8 /* NSValue+PlatformIndependence.h in Headers */ = {isa = PBXBuildFile; fileRef = 540F65A518F82347005A6BB8 /* NSValue+PlatformIndependence.h */; settings = {ATTRIBUTES = (Private, ); }; }; 540F65A818F82347005A6BB8 /* NSValue+PlatformIndependence.m in Sources */ = {isa = PBXBuildFile; fileRef = 540F65A618F82347005A6BB8 /* NSValue+PlatformIndependence.m */; }; @@ -24,6 +30,8 @@ 545D5ABF180B2D3F00FC94AB /* RBBCubicBezier.m in Sources */ = {isa = PBXBuildFile; fileRef = 545D5ABE180B2D3F00FC94AB /* RBBCubicBezier.m */; }; 545D5AC2180B5E8400FC94AB /* RBBEasingFunction.m in Sources */ = {isa = PBXBuildFile; fileRef = 545D5AC1180B5E8400FC94AB /* RBBEasingFunction.m */; }; 545D5AC5180C138D00FC94AB /* RBBSpringAnimation.m in Sources */ = {isa = PBXBuildFile; fileRef = 545D5AC4180C138D00FC94AB /* RBBSpringAnimation.m */; }; + 545FEFD618F177B000900B78 /* RBBRubberbandAnimation.m in Sources */ = {isa = PBXBuildFile; fileRef = 545FEFD518F177B000900B78 /* RBBRubberbandAnimation.m */; }; + 5466A9B518F6C2260090E3E6 /* RBBDampedHarmonicOscillaton.m in Sources */ = {isa = PBXBuildFile; fileRef = 5466A9B418F6C2260090E3E6 /* RBBDampedHarmonicOscillaton.m */; }; 5466C56118082BEB00652BDC /* RBBBlockBasedArray.m in Sources */ = {isa = PBXBuildFile; fileRef = 5466C56018082BEB00652BDC /* RBBBlockBasedArray.m */; }; 5466C56318082D4200652BDC /* RBBBlockBasedArraySpec.m in Sources */ = {isa = PBXBuildFile; fileRef = 5466C56218082D4200652BDC /* RBBBlockBasedArraySpec.m */; }; 5466C569180849D100652BDC /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 54F3AE2D1807394800E1E688 /* Foundation.framework */; }; @@ -37,6 +45,8 @@ 5466C59818084BA200652BDC /* RBBAnimationViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 5466C59718084BA200652BDC /* RBBAnimationViewController.m */; }; 5479BEA4181D8008009811FD /* RBBCustomAnimation.m in Sources */ = {isa = PBXBuildFile; fileRef = 5479BEA3181D8008009811FD /* RBBCustomAnimation.m */; }; 549ECA8418F6ECFC00D968C7 /* RBBSpringAnimationSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = 549ECA8318F6ECFC00D968C7 /* RBBSpringAnimationSpec.m */; }; + 549ECA8618F7010400D968C7 /* RBBDampedHarmonicOscillatonSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = 549ECA8518F7010400D968C7 /* RBBDampedHarmonicOscillatonSpec.m */; }; + 549ECA8818F7028E00D968C7 /* RBBRubberbandAnimationSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = 549ECA8718F7028E00D968C7 /* RBBRubberbandAnimationSpec.m */; }; 549ECA9618F814A000D968C7 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 549ECA9518F814A000D968C7 /* Cocoa.framework */; }; 549ECAAA18F814A000D968C7 /* XCTest.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 54F3AE3B1807394800E1E688 /* XCTest.framework */; }; 549ECAAB18F814A000D968C7 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 549ECA9518F814A000D968C7 /* Cocoa.framework */; }; @@ -133,6 +143,10 @@ 545D5AC1180B5E8400FC94AB /* RBBEasingFunction.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RBBEasingFunction.m; sourceTree = ""; }; 545D5AC3180C138D00FC94AB /* RBBSpringAnimation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RBBSpringAnimation.h; sourceTree = ""; }; 545D5AC4180C138D00FC94AB /* RBBSpringAnimation.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RBBSpringAnimation.m; sourceTree = ""; }; + 545FEFD418F177B000900B78 /* RBBRubberbandAnimation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RBBRubberbandAnimation.h; sourceTree = ""; }; + 545FEFD518F177B000900B78 /* RBBRubberbandAnimation.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RBBRubberbandAnimation.m; sourceTree = ""; }; + 5466A9B318F6C2260090E3E6 /* RBBDampedHarmonicOscillaton.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RBBDampedHarmonicOscillaton.h; sourceTree = ""; }; + 5466A9B418F6C2260090E3E6 /* RBBDampedHarmonicOscillaton.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RBBDampedHarmonicOscillaton.m; sourceTree = ""; }; 5466C55F18082BEB00652BDC /* RBBBlockBasedArray.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RBBBlockBasedArray.h; sourceTree = ""; }; 5466C56018082BEB00652BDC /* RBBBlockBasedArray.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RBBBlockBasedArray.m; sourceTree = ""; }; 5466C56218082D4200652BDC /* RBBBlockBasedArraySpec.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RBBBlockBasedArraySpec.m; sourceTree = ""; }; @@ -150,6 +164,8 @@ 5479BEA2181D8008009811FD /* RBBCustomAnimation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RBBCustomAnimation.h; sourceTree = ""; }; 5479BEA3181D8008009811FD /* RBBCustomAnimation.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RBBCustomAnimation.m; sourceTree = ""; }; 549ECA8318F6ECFC00D968C7 /* RBBSpringAnimationSpec.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RBBSpringAnimationSpec.m; sourceTree = ""; }; + 549ECA8518F7010400D968C7 /* RBBDampedHarmonicOscillatonSpec.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RBBDampedHarmonicOscillatonSpec.m; sourceTree = ""; }; + 549ECA8718F7028E00D968C7 /* RBBRubberbandAnimationSpec.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RBBRubberbandAnimationSpec.m; sourceTree = ""; }; 549ECA9418F814A000D968C7 /* RBBAnimation Mac.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = "RBBAnimation Mac.framework"; sourceTree = BUILT_PRODUCTS_DIR; }; 549ECA9518F814A000D968C7 /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = Library/Frameworks/Cocoa.framework; sourceTree = DEVELOPER_DIR; }; 549ECA9818F814A000D968C7 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; @@ -326,6 +342,10 @@ 545D5AB9180B1FAE00FC94AB /* RBBTweenAnimation.m */, 545D5AC3180C138D00FC94AB /* RBBSpringAnimation.h */, 545D5AC4180C138D00FC94AB /* RBBSpringAnimation.m */, + 545FEFD418F177B000900B78 /* RBBRubberbandAnimation.h */, + 545FEFD518F177B000900B78 /* RBBRubberbandAnimation.m */, + 5466A9B318F6C2260090E3E6 /* RBBDampedHarmonicOscillaton.h */, + 5466A9B418F6C2260090E3E6 /* RBBDampedHarmonicOscillaton.m */, 54D5EA87181AE8620014896C /* RBBLinearInterpolation.h */, 54D5EA88181AE8620014896C /* RBBLinearInterpolation.m */, 545D5AC0180B5E8400FC94AB /* RBBEasingFunction.h */, @@ -358,8 +378,10 @@ isa = PBXGroup; children = ( 5466C56218082D4200652BDC /* RBBBlockBasedArraySpec.m */, + 549ECA8518F7010400D968C7 /* RBBDampedHarmonicOscillatonSpec.m */, 540F65AA18F82B3E005A6BB8 /* RBBLinearInterpolationSpec.m */, 549ECA8318F6ECFC00D968C7 /* RBBSpringAnimationSpec.m */, + 549ECA8718F7028E00D968C7 /* RBBRubberbandAnimationSpec.m */, 54F3AE441807394800E1E688 /* Supporting Files */, ); path = Specs; @@ -386,6 +408,8 @@ 549ECAC418F8157200D968C7 /* RBBSpringAnimation.h in Headers */, 549ECACA18F8157200D968C7 /* RBBCubicBezier.h in Headers */, 549ECAC018F8157200D968C7 /* RBBCustomAnimation.h in Headers */, + 540935651905223A007565F7 /* RBBDampedHarmonicOscillaton.h in Headers */, + 540935661905223D007565F7 /* RBBRubberbandAnimation.h in Headers */, 549ECAC618F8157200D968C7 /* RBBLinearInterpolation.h in Headers */, 540F65A718F82347005A6BB8 /* NSValue+PlatformIndependence.h in Headers */, 540F65AF18F83EB3005A6BB8 /* NSColor+PlatformIndependence.h in Headers */, @@ -695,6 +719,8 @@ 549ECAC718F8157200D968C7 /* RBBLinearInterpolation.m in Sources */, 549ECABF18F8157200D968C7 /* RBBAnimation.m in Sources */, 549ECACD18F8157200D968C7 /* RBBBlockBasedArray.m in Sources */, + 5409356719052241007565F7 /* RBBRubberbandAnimation.m in Sources */, + 5409356419052226007565F7 /* RBBDampedHarmonicOscillaton.m in Sources */, 549ECACB18F8157200D968C7 /* RBBCubicBezier.m in Sources */, 540F65A918F82347005A6BB8 /* NSValue+PlatformIndependence.m in Sources */, ); @@ -707,6 +733,8 @@ 549ECABD18F8155800D968C7 /* RBBBlockBasedArraySpec.m in Sources */, 549ECABE18F8155B00D968C7 /* RBBSpringAnimationSpec.m in Sources */, 540F65AC18F82C67005A6BB8 /* RBBLinearInterpolationSpec.m in Sources */, + 5409356919052251007565F7 /* RBBRubberbandAnimationSpec.m in Sources */, + 540935681905224C007565F7 /* RBBDampedHarmonicOscillatonSpec.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -720,6 +748,8 @@ 54D5EA89181AE8620014896C /* RBBLinearInterpolation.m in Sources */, 545D5AC2180B5E8400FC94AB /* RBBEasingFunction.m in Sources */, 5466C56118082BEB00652BDC /* RBBBlockBasedArray.m in Sources */, + 545FEFD618F177B000900B78 /* RBBRubberbandAnimation.m in Sources */, + 5466A9B518F6C2260090E3E6 /* RBBDampedHarmonicOscillaton.m in Sources */, 545D5ABA180B1FAE00FC94AB /* RBBTweenAnimation.m in Sources */, 545D5AC5180C138D00FC94AB /* RBBSpringAnimation.m in Sources */, 54F3AE351807394800E1E688 /* RBBAnimation.m in Sources */, @@ -733,6 +763,8 @@ files = ( 5466C56318082D4200652BDC /* RBBBlockBasedArraySpec.m in Sources */, 549ECA8418F6ECFC00D968C7 /* RBBSpringAnimationSpec.m in Sources */, + 549ECA8618F7010400D968C7 /* RBBDampedHarmonicOscillatonSpec.m in Sources */, + 549ECA8818F7028E00D968C7 /* RBBRubberbandAnimationSpec.m in Sources */, 540F65AB18F82B3E005A6BB8 /* RBBLinearInterpolationSpec.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/RBBAnimation/NSValue+PlatformIndependence.h b/RBBAnimation/NSValue+PlatformIndependence.h index dd29d0e..e58428b 100644 --- a/RBBAnimation/NSValue+PlatformIndependence.h +++ b/RBBAnimation/NSValue+PlatformIndependence.h @@ -6,6 +6,10 @@ // Copyright (c) 2014 Robert Böhnke. All rights reserved. // +#if TARGET_OS_IPHONE || TARGET_IPHONE_SIMULATOR +#import +#endif + #import @interface NSValue (PlatformIndependence) diff --git a/RBBAnimation/NSValue+PlatformIndependence.m b/RBBAnimation/NSValue+PlatformIndependence.m index da04b5a..c56c172 100644 --- a/RBBAnimation/NSValue+PlatformIndependence.m +++ b/RBBAnimation/NSValue+PlatformIndependence.m @@ -6,10 +6,6 @@ // Copyright (c) 2014 Robert Böhnke. All rights reserved. // -#if TARGET_OS_IPHONE || TARGET_IPHONE_SIMULATOR -#import -#endif - #import "NSValue+PlatformIndependence.h" @implementation NSValue (PlatformIndependence) diff --git a/RBBAnimation/RBBDampedHarmonicOscillaton.h b/RBBAnimation/RBBDampedHarmonicOscillaton.h new file mode 100644 index 0000000..e180e90 --- /dev/null +++ b/RBBAnimation/RBBDampedHarmonicOscillaton.h @@ -0,0 +1,13 @@ +// +// RBBDampedHarmonicOscillaton.h +// RBBAnimation +// +// Created by Robert Böhnke on 10/04/14. +// Copyright (c) 2014 Robert Böhnke. All rights reserved. +// + +#import + +typedef CGFloat (^RBBOsciallation)(CGFloat t); + +RBBOsciallation RBBDampedHarmonicOscillation(CGFloat x0, CGFloat damping, CGFloat mass, CGFloat stiffness, CGFloat velocity, BOOL allowsOverdamping); diff --git a/RBBAnimation/RBBDampedHarmonicOscillaton.m b/RBBAnimation/RBBDampedHarmonicOscillaton.m new file mode 100644 index 0000000..9094813 --- /dev/null +++ b/RBBAnimation/RBBDampedHarmonicOscillaton.m @@ -0,0 +1,45 @@ +// +// RBBDampedHarmonicOscillaton.m +// RBBAnimation +// +// Created by Robert Böhnke on 10/04/14. +// Copyright (c) 2014 Robert Böhnke. All rights reserved. +// + +#import "RBBDampedHarmonicOscillaton.h" + +RBBOsciallation RBBDampedHarmonicOscillation(CGFloat x0, CGFloat b, CGFloat m, CGFloat k, CGFloat v0, BOOL allowsOverdamping) { + NSCAssert(m > 0, @"mass must be greater than zero."); + NSCAssert(k > 0, @"stiffness must be greater than zero."); + NSCAssert(b > 0, @"damping must be greater than zero."); + + CGFloat beta = b / (2 * m); + CGFloat omega0 = sqrtf(k / m); + CGFloat omega1 = sqrtf((omega0 * omega0) - (beta * beta)); + CGFloat omega2 = sqrtf((beta * beta) - (omega0 * omega0)); + + if (!allowsOverdamping && beta > omega0) beta = omega0; + + if (beta < omega0) { + // Underdamped + return ^(CGFloat t) { + CGFloat envelope = expf(-beta * t); + + return envelope * (x0 * cosf(omega1 * t) + ((beta * x0 + v0) / omega1) * sinf(omega1 * t)); + }; + } else if (beta == omega0) { + // Critically damped + return ^(CGFloat t) { + CGFloat envelope = expf(-beta * t); + + return envelope * (x0 + (beta * x0 + v0) * t); + }; + } else { + // Overdamped + return ^(CGFloat t) { + CGFloat envelope = expf(-beta * t); + + return envelope * (x0 * coshf(omega2 * t) + ((beta * x0 + v0) / omega2) * sinhf(omega2 * t)); + }; + } +} diff --git a/RBBAnimation/RBBRubberbandAnimation.h b/RBBAnimation/RBBRubberbandAnimation.h new file mode 100644 index 0000000..1832b27 --- /dev/null +++ b/RBBAnimation/RBBRubberbandAnimation.h @@ -0,0 +1,27 @@ +// +// RBBRubberbandAnimation.h +// RBBAnimation +// +// Created by Robert Böhnke on 06/04/14. +// Copyright (c) 2014 Robert Böhnke. All rights reserved. +// + +#import + +#import "RBBAnimation.h" + +@interface RBBRubberbandAnimation : RBBAnimation + +@property (readwrite, nonatomic, assign) CGFloat damping; +@property (readwrite, nonatomic, assign) CGFloat mass; +@property (readwrite, nonatomic, assign) CGFloat stiffness; +@property (readwrite, nonatomic, assign) CGPoint velocity; + +@property (readwrite, nonatomic, assign) CGPoint from; +@property (readwrite, nonatomic, assign) CGPoint to; + +@property (readwrite, nonatomic, assign) BOOL allowsOverdamping; + +- (CFTimeInterval)durationForEpsilon:(double)epsilon; + +@end diff --git a/RBBAnimation/RBBRubberbandAnimation.m b/RBBAnimation/RBBRubberbandAnimation.m new file mode 100644 index 0000000..1d565d3 --- /dev/null +++ b/RBBAnimation/RBBRubberbandAnimation.m @@ -0,0 +1,95 @@ +// +// RBBRubberbandAnimation.m +// RBBAnimation +// +// Created by Robert Böhnke on 06/04/14. +// Copyright (c) 2014 Robert Böhnke. All rights reserved. +// + +#import "NSValue+PlatformIndependence.h" + +#import "RBBDampedHarmonicOscillaton.h" + +#import "RBBRubberbandAnimation.h" + +@implementation RBBRubberbandAnimation + +#pragma mark - Lifecycle + +- (id)init { + self = [super init]; + if (self == nil) return nil; + + self.damping = 10; + self.mass = 1; + self.stiffness = 100; + + return self; +} + +#pragma mark - KVO + ++ (NSSet *)keyPathsForValuesAffectingAnimationBlock { + return [NSSet setWithArray:@[ + @"damping", + @"mass", + @"stiffness", + @"velocity", + @"from", + @"to", + @"allowsOverdamping" + ]]; +} + +#pragma mark - RBBSpringAnimation + +- (CFTimeInterval)durationForEpsilon:(double)epsilon { + CGFloat beta = self.damping / (2 * self.mass); + + CFTimeInterval duration = 0; + while (expf(-beta * duration) >= epsilon) { + duration += 0.1; + } + + return duration; +} + +#pragma mark - RBBAnimation + +- (RBBAnimationBlock)animationBlock { + CGFloat deltaX = self.from.x - self.to.x; + CGFloat deltaY = self.from.y - self.to.y; + + RBBOsciallation oscillationX = RBBDampedHarmonicOscillation(deltaX, self.damping, self.mass, self.stiffness, self.velocity.x, self.allowsOverdamping); + RBBOsciallation oscillationY = RBBDampedHarmonicOscillation(deltaY, self.damping, self.mass, self.stiffness, self.velocity.y, self.allowsOverdamping); + + CGFloat x0 = self.to.x; + CGFloat y0 = self.to.y; + + return ^(CGFloat t, CGFloat _) { + CGPoint p = { .x = x0 + oscillationX(t), .y = y0 + oscillationY(t) }; + + return [NSValue rbb_valueWithCGPoint:p]; + }; +} + +#pragma mark - NSObject + +- (id)copyWithZone:(NSZone *)zone { + RBBRubberbandAnimation *copy = [super copyWithZone:zone]; + if (copy == nil) return nil; + + copy->_damping = _damping; + copy->_mass = _mass; + copy->_stiffness = _stiffness; + copy->_velocity = _velocity; + + copy->_from = _from; + copy->_to = _to; + + copy->_allowsOverdamping = _allowsOverdamping; + + return copy; +} + +@end diff --git a/RBBAnimation/RBBSpringAnimation.m b/RBBAnimation/RBBSpringAnimation.m index 348429a..121725c 100644 --- a/RBBAnimation/RBBSpringAnimation.m +++ b/RBBAnimation/RBBSpringAnimation.m @@ -7,6 +7,7 @@ // #import "RBBBlockBasedArray.h" +#import "RBBDampedHarmonicOscillaton.h" #import "RBBLinearInterpolation.h" #import "RBBSpringAnimation.h" @@ -58,51 +59,11 @@ - (CFTimeInterval)durationForEpsilon:(double)epsilon { #pragma mark - RBBAnimation - (RBBAnimationBlock)animationBlock { - CGFloat b = self.damping; - CGFloat m = self.mass; - CGFloat k = self.stiffness; - CGFloat v0 = self.velocity; - - NSParameterAssert(m > 0); - NSParameterAssert(k > 0); - NSParameterAssert(b > 0); - - CGFloat beta = b / (2 * m); - CGFloat omega0 = sqrtf(k / m); - CGFloat omega1 = sqrtf((omega0 * omega0) - (beta * beta)); - CGFloat omega2 = sqrtf((beta * beta) - (omega0 * omega0)); - - CGFloat x0 = -1; - - if (!self.allowsOverdamping && beta > omega0) beta = omega0; - - CGFloat (^oscillation)(CGFloat); - if (beta < omega0) { - // Underdamped - oscillation = ^(CGFloat t) { - CGFloat envelope = expf(-beta * t); - - return -x0 + envelope * (x0 * cosf(omega1 * t) + ((beta * x0 + v0) / omega1) * sinf(omega1 * t)); - }; - } else if (beta == omega0) { - // Critically damped - oscillation = ^(CGFloat t) { - CGFloat envelope = expf(-beta * t); - - return -x0 + envelope * (x0 + (beta * x0 + v0) * t); - }; - } else { - // Overdamped - oscillation = ^(CGFloat t) { - CGFloat envelope = expf(-beta * t); - - return -x0 + envelope * (x0 * coshf(omega2 * t) + ((beta * x0 + v0) / omega2) * sinhf(omega2 * t)); - }; - } + CGFloat (^oscillation)(CGFloat) = RBBDampedHarmonicOscillation(-1, self.damping, self.mass, self.stiffness, self.velocity, self.allowsOverdamping); RBBLinearInterpolation lerp = RBBInterpolate(self.fromValue, self.toValue); return ^(CGFloat t, CGFloat _) { - return lerp(oscillation(t)); + return lerp(1 + oscillation(t)); }; } diff --git a/RBBAnimationTest/RBBAnimationListViewController.m b/RBBAnimationTest/RBBAnimationListViewController.m index 8587956..5a82269 100644 --- a/RBBAnimationTest/RBBAnimationListViewController.m +++ b/RBBAnimationTest/RBBAnimationListViewController.m @@ -13,6 +13,7 @@ #import "RBBCustomAnimation.h" #import "RBBTweenAnimation.h" #import "RBBSpringAnimation.h" +#import "RBBRubberbandAnimation.h" #import "RBBAnimationListViewController.h" @@ -62,6 +63,19 @@ - (void)viewDidLoad { spring.duration = [spring durationForEpsilon:0.01]; spring.rbb_name = @"spring"; + RBBRubberbandAnimation *rubberband = [RBBRubberbandAnimation animationWithKeyPath:@"position"]; + + rubberband.from = CGPointMake(0, -100); + rubberband.to = CGPointMake(0, 100); + rubberband.mass = 1; + rubberband.damping = 10; + rubberband.stiffness = 100; + rubberband.velocity = CGPointMake(0, 0); + + rubberband.additive = YES; + rubberband.duration = [rubberband durationForEpsilon:0.01]; + rubberband.rbb_name = @"rubberband"; + RBBTweenAnimation *sinus = [RBBTweenAnimation animationWithKeyPath:@"position.y"]; sinus.fromValue = @(0); sinus.toValue = @(100); @@ -93,6 +107,7 @@ - (void)viewDidLoad { self.animations = @[ easeInOutBack, spring, + rubberband, scale, sinus, bounce, diff --git a/Specs/RBBDampedHarmonicOscillatonSpec.m b/Specs/RBBDampedHarmonicOscillatonSpec.m new file mode 100644 index 0000000..59d0f5a --- /dev/null +++ b/Specs/RBBDampedHarmonicOscillatonSpec.m @@ -0,0 +1,31 @@ +// +// RBBDampedHarmonicOscillatonSpec.m +// RBBAnimation +// +// Created by Robert Böhnke on 10/04/14. +// Copyright (c) 2014 Robert Böhnke. All rights reserved. +// + +#import "RBBDampedHarmonicOscillaton.h" + +SpecBegin(RBBDampedHarmonicOscillaton) + +it(@"should return correct values for fix points", ^{ + RBBOsciallation oscillation = RBBDampedHarmonicOscillation(1, 10, 1, 100, 0, YES); + + expect(oscillation(0.0)).to.beCloseToWithin( 1.00, 0.01); + expect(oscillation(0.1)).to.beCloseToWithin( 0.65, 0.01); + expect(oscillation(0.2)).to.beCloseToWithin( 0.15, 0.01); + expect(oscillation(0.3)).to.beCloseToWithin(-0.12, 0.01); + expect(oscillation(0.4)).to.beCloseToWithin(-0.15, 0.01); + expect(oscillation(0.5)).to.beCloseToWithin(-0.07, 0.01); + expect(oscillation(0.6)).to.beCloseToWithin( 0.00, 0.01); + expect(oscillation(0.7)).to.beCloseToWithin( 0.02, 0.01); + expect(oscillation(0.8)).to.beCloseToWithin( 0.02, 0.01); + expect(oscillation(0.9)).to.beCloseToWithin( 0.00, 0.01); + expect(oscillation(1.0)).to.beCloseToWithin( 0.00, 0.01); + + expect(oscillation(100)).to.beCloseToWithin(0, 0.01); +}); + +SpecEnd diff --git a/Specs/RBBLinearInterpolationSpec.m b/Specs/RBBLinearInterpolationSpec.m index 1d1c8ad..a7a1bc8 100644 --- a/Specs/RBBLinearInterpolationSpec.m +++ b/Specs/RBBLinearInterpolationSpec.m @@ -7,14 +7,10 @@ // #if TARGET_OS_IPHONE || TARGET_IPHONE_SIMULATOR -#import - #import "UIColor+PlatformIndependence.h" #define SpecsColor UIColor #else -#import - #import "NSColor+PlatformIndependence.h" #define SpecsColor NSColor diff --git a/Specs/RBBRubberbandAnimationSpec.m b/Specs/RBBRubberbandAnimationSpec.m new file mode 100644 index 0000000..079194a --- /dev/null +++ b/Specs/RBBRubberbandAnimationSpec.m @@ -0,0 +1,53 @@ +// +// RBBRubberbandAnimationSpec.m +// RBBAnimation +// +// Created by Robert Böhnke on 10/04/14. +// Copyright (c) 2014 Robert Böhnke. All rights reserved. +// + +#import "NSValue+PlatformIndependence.h" + +#import "RBBSpringAnimation.h" + +#import "RBBRubberbandAnimation.h" + +SpecBegin(RBBRubberbandAnimation) + +it(@"should initialize", ^{ + expect([RBBRubberbandAnimation animation]).notTo.beNil(); + + expect([RBBRubberbandAnimation animationWithKeyPath:@"position"]).notTo.beNil(); +}); + +it(@"should behave like RBBSpringAnimations if v is 0", ^{ + RBBSpringAnimation *spring = [RBBSpringAnimation animationWithKeyPath:@"position"]; + spring.fromValue = [NSValue rbb_valueWithCGPoint:CGPointMake(100, 200)]; + spring.toValue = [NSValue rbb_valueWithCGPoint:CGPointMake(300, 400)]; + spring.velocity = 0; + + RBBRubberbandAnimation *rubberband = [RBBRubberbandAnimation animationWithKeyPath:@"position"]; + rubberband.from = CGPointMake(100, 200); + rubberband.to = CGPointMake(300, 400); + rubberband.velocity = CGPointZero; + + expect(rubberband.values).to.equal(spring.values); +}); + +it(@"should take the velocity into account even if ∆x and ∆y are 0", ^{ + RBBRubberbandAnimation *animation = [RBBRubberbandAnimation animationWithKeyPath:@"position"]; + animation.from = CGPointZero; + animation.to = CGPointZero; + animation.velocity = CGPointMake(500, 0); + animation.duration = 1; + + CGFloat maxX = -HUGE_VALF; + + for (NSValue *value in animation.values) { + if (value.rbb_CGPointValue.x > maxX) maxX = value.rbb_CGPointValue.x; + } + + expect(maxX).to.beCloseToWithin(27, 0.5); +}); + +SpecEnd diff --git a/Specs/Specs-Prefix.pch b/Specs/Specs-Prefix.pch index c6549aa..68e913f 100644 --- a/Specs/Specs-Prefix.pch +++ b/Specs/Specs-Prefix.pch @@ -1,6 +1,12 @@ #ifdef __OBJC__ #import + #if TARGET_OS_IPHONE || TARGET_IPHONE_SIMULATOR + #import + #else + #import + #endif + #import #define EXP_SHORTHAND