Skip to content

Commit

Permalink
Merge pull request #6 from nytm/fix/noise-filter
Browse files Browse the repository at this point in the history
Add a low-pass noise filter.
  • Loading branch information
cdzombak authored Aug 2, 2016
2 parents 785eeaf + ab2c5d6 commit 68d73c4
Show file tree
Hide file tree
Showing 4 changed files with 78 additions and 5 deletions.
2 changes: 1 addition & 1 deletion Sources/NYT360CameraController.m
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ - (void)updateCameraAngle {
CMRotationRate rotationRate = self.motionManager.deviceMotion.rotationRate;
UIInterfaceOrientation orientation = [UIApplication sharedApplication].statusBarOrientation;
NYT360EulerAngleCalculationResult result;
result = NYT360DeviceMotionCalculation(self.currentPosition, rotationRate, orientation, self.allowedPanningAxes);
result = NYT360DeviceMotionCalculation(self.currentPosition, rotationRate, orientation, self.allowedPanningAxes, NYT360EulerAngleCalculationNoiseThresholdDefault);
self.currentPosition = result.position;
self.camera.eulerAngles = result.eulerAngles;
}
Expand Down
4 changes: 3 additions & 1 deletion Sources/NYT360EulerAngleCalculations.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
@import SceneKit;
@import CoreMotion;

extern CGFloat const NYT360EulerAngleCalculationNoiseThresholdDefault;

#import "NYT360DataTypes.h"

#pragma mark - Data Types
Expand All @@ -24,6 +26,6 @@ typedef struct NYT360EulerAngleCalculationResult NYT360EulerAngleCalculationResu

NYT360EulerAngleCalculationResult NYT360UpdatedPositionAndAnglesForAllowedAxes(CGPoint position, NYT360PanningAxis allowedPanningAxes);

NYT360EulerAngleCalculationResult NYT360DeviceMotionCalculation(CGPoint position, CMRotationRate rotationRate, UIInterfaceOrientation orientation, NYT360PanningAxis allowedPanningAxes);
NYT360EulerAngleCalculationResult NYT360DeviceMotionCalculation(CGPoint position, CMRotationRate rotationRate, UIInterfaceOrientation orientation, NYT360PanningAxis allowedPanningAxes, CGFloat noiseThreshold);

NYT360EulerAngleCalculationResult NYT360PanGestureChangeCalculation(CGPoint position, CGPoint rotateDelta, CGSize viewSize, NYT360PanningAxis allowedPanningAxes);
25 changes: 24 additions & 1 deletion Sources/NYT360EulerAngleCalculations.m
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

#pragma mark - Constants

CGFloat const NYT360EulerAngleCalculationNoiseThresholdDefault = 0.12;
static CGFloat NYT360EulerAngleCalculationRotationRateDampingFactor = 0.02;

#pragma mark - Inline Functions
Expand Down Expand Up @@ -45,7 +46,29 @@ NYT360EulerAngleCalculationResult NYT360UpdatedPositionAndAnglesForAllowedAxes(C
return NYT360EulerAngleCalculationResultMake(position, eulerAngles);
}

NYT360EulerAngleCalculationResult NYT360DeviceMotionCalculation(CGPoint position, CMRotationRate rotationRate, UIInterfaceOrientation orientation, NYT360PanningAxis allowedPanningAxes) {
NYT360EulerAngleCalculationResult NYT360DeviceMotionCalculation(CGPoint position, CMRotationRate rotationRate, UIInterfaceOrientation orientation, NYT360PanningAxis allowedPanningAxes, CGFloat noiseThreshold) {

// On some devices, the rotation rates exhibit a low-level drift on one or
// more rotation axes. The symptom expressions are not identical, but they
// appear to be related to low component quality (iPhone 5c versus higher
// end devices) and/or rough usage (drops, etc). In an ideal scenario, we
// could ask users to calibrate their gyroscopes and apply a corrective
// factor to all inputs. Barring that, the next best thing we can try is to
// add a low-pass filter which ignores input less than a given threshold.
// In my non-scientific testing with the only affected devices at my
// disposal, I found that a noise threshold between 0.10 and 0.15 filtered
// out the noise with a minimal loss in sensitivity. Less than 0.10 and the
// 360 camera position starts to drift.
// ~ Jared Sinclair, August 1, 2016.
// See also: https://forums.developer.apple.com/thread/12049

if (fabs(rotationRate.x) < noiseThreshold) {
rotationRate.x = 0;
}

if (fabs(rotationRate.y) < noiseThreshold) {
rotationRate.y = 0;
}

CGFloat damping = NYT360EulerAngleCalculationRotationRateDampingFactor;

Expand Down
52 changes: 50 additions & 2 deletions Three60_PlayerTests/NYT360EulerAngleCalculationsTests.m
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ - (void)testDeviceMotionFunctionShouldZeroOutDisallowedYAxis {
rate.y = -1000;
rate.z = 10;
UIInterfaceOrientation orientation = UIInterfaceOrientationLandscapeLeft;
NYT360EulerAngleCalculationResult result = NYT360DeviceMotionCalculation(position, rate, orientation, NYT360PanningAxisHorizontal);
NYT360EulerAngleCalculationResult result = NYT360DeviceMotionCalculation(position, rate, orientation, NYT360PanningAxisHorizontal, NYT360EulerAngleCalculationNoiseThresholdDefault);
XCTAssertNotEqual(result.position.x, 0);
XCTAssertEqual(result.position.y, 0);
}
Expand All @@ -50,11 +50,59 @@ - (void)testDeviceMotionFunctionShouldZeroOutDisallowedXAxis {
rate.y = -1000;
rate.z = 10;
UIInterfaceOrientation orientation = UIInterfaceOrientationLandscapeLeft;
NYT360EulerAngleCalculationResult result = NYT360DeviceMotionCalculation(position, rate, orientation, NYT360PanningAxisVertical);
NYT360EulerAngleCalculationResult result = NYT360DeviceMotionCalculation(position, rate, orientation, NYT360PanningAxisVertical, NYT360EulerAngleCalculationNoiseThresholdDefault);
XCTAssertEqual(result.position.x, 0);
XCTAssertNotEqual(result.position.y, 0);
}

- (void)testDeviceMotionFunctionShouldFilterOutNegativeXRotationNoise {
CGPoint position = CGPointMake(100, 100);
CMRotationRate rate;
rate.x = NYT360EulerAngleCalculationNoiseThresholdDefault * -0.5;
rate.y = NYT360EulerAngleCalculationNoiseThresholdDefault * 2;
rate.z = 10;
UIInterfaceOrientation orientation = UIInterfaceOrientationLandscapeLeft;
NYT360EulerAngleCalculationResult result = NYT360DeviceMotionCalculation(position, rate, orientation, NYT360PanningAxisHorizontal, NYT360EulerAngleCalculationNoiseThresholdDefault);
XCTAssertEqual(result.position.x, position.x);
XCTAssertNotEqual(result.position.y, position.y);
}

- (void)testDeviceMotionFunctionShouldFilterOutPositiveXRotationNoise {
CGPoint position = CGPointMake(100, 100);
CMRotationRate rate;
rate.x = NYT360EulerAngleCalculationNoiseThresholdDefault * 0.5;
rate.y = NYT360EulerAngleCalculationNoiseThresholdDefault * 2;
rate.z = 10;
UIInterfaceOrientation orientation = UIInterfaceOrientationLandscapeLeft;
NYT360EulerAngleCalculationResult result = NYT360DeviceMotionCalculation(position, rate, orientation, NYT360PanningAxisHorizontal, NYT360EulerAngleCalculationNoiseThresholdDefault);
XCTAssertEqual(result.position.x, position.x);
XCTAssertNotEqual(result.position.y, position.y);
}

- (void)testDeviceMotionFunctionShouldFilterOutNegativeYRotationNoise {
CGPoint position = CGPointMake(100, 100);
CMRotationRate rate;
rate.x = NYT360EulerAngleCalculationNoiseThresholdDefault * 2;
rate.y = NYT360EulerAngleCalculationNoiseThresholdDefault * -0.5;
rate.z = 10;
UIInterfaceOrientation orientation = UIInterfaceOrientationLandscapeLeft;
NYT360EulerAngleCalculationResult result = NYT360DeviceMotionCalculation(position, rate, orientation, NYT360PanningAxisHorizontal, NYT360EulerAngleCalculationNoiseThresholdDefault);
XCTAssertNotEqual(result.position.x, 0);
XCTAssertEqual(result.position.y, 0);
}

- (void)testDeviceMotionFunctionShouldFilterOutPositiveYRotationNoise {
CGPoint position = CGPointMake(100, 100);
CMRotationRate rate;
rate.x = NYT360EulerAngleCalculationNoiseThresholdDefault * 2;
rate.y = NYT360EulerAngleCalculationNoiseThresholdDefault * 0.5;
rate.z = 10;
UIInterfaceOrientation orientation = UIInterfaceOrientationLandscapeLeft;
NYT360EulerAngleCalculationResult result = NYT360DeviceMotionCalculation(position, rate, orientation, NYT360PanningAxisHorizontal, NYT360EulerAngleCalculationNoiseThresholdDefault);
XCTAssertNotEqual(result.position.x, 0);
XCTAssertEqual(result.position.y, 0);
}

- (void)testPanGestureChangeFunctionShouldZeroOutDisallowedYAxis {
CGPoint position = CGPointMake(100, 100);
CGPoint delta = CGPointMake(1000, -1000);
Expand Down

0 comments on commit 68d73c4

Please sign in to comment.