Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Groundwork for Feature MO-7031 #3

Merged
merged 16 commits into from
Jul 28, 2016
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 18 additions & 1 deletion Sources/NYT360CameraController.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@

@import UIKit;

#import "NYT360DataTypes.h"

@class SCNView;

NS_ASSUME_NONNULL_BEGIN
Expand All @@ -23,10 +25,25 @@ NS_ASSUME_NONNULL_BEGIN
- (void)startMotionUpdates;
- (void)stopMotionUpdates;

#pragma mark -
#pragma mark - Camera Angle Updates

- (void)updateCameraAngle;

#pragma mark - Panning Options

/**
Changing this property will allow you to suppress undesired range of motion
along either the x or y axis. For example, y axis input should be suppressed
when a 360 video is playing inline in a scroll view.

When this property is set, any disallowed axis will cause the current camera
angles to be clamped to zero for that axis. Existing angles for the any allowed
axes will not be affected.

Defaults to NYT360PanningAxisHorizontal | NYT360PanningAxisVertical.
*/
@property (nonatomic, assign) NYT360PanningAxis allowedPanningAxes;

@end

NS_ASSUME_NONNULL_END
47 changes: 18 additions & 29 deletions Sources/NYT360CameraController.m
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,9 @@
@import CoreMotion;

#import "NYT360CameraController.h"
#import "NYT360EulerAngleCalculations.h"

#define CLAMP(x, low, high) (((x) > (high)) ? (high) : (((x) < (low)) ? (low) : (x)))

CGPoint subtractPoints(CGPoint a, CGPoint b) {
static inline CGPoint subtractPoints(CGPoint a, CGPoint b) {
return CGPointMake(b.x - a.x, b.y - a.y);
}

Expand All @@ -39,6 +38,7 @@ - (id)initWithView:(SCNView *)view {
_camera = view.pointOfView;
_view = view;
_currentPosition = CGPointMake(0, 0);
_allowedPanningAxes = NYT360PanningAxisHorizontal | NYT360PanningAxisVertical;

_panRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(handlePan:)];
_panRecognizer.delegate = self;
Expand Down Expand Up @@ -68,25 +68,18 @@ - (void)updateCameraAngle {

CMRotationRate rotationRate = self.motionManager.deviceMotion.rotationRate;
UIInterfaceOrientation orientation = [UIApplication sharedApplication].statusBarOrientation;
// TODO: [thiago] I think this can be simplified later
if (UIInterfaceOrientationIsLandscape(orientation)) {
if (orientation == UIInterfaceOrientationLandscapeLeft) {
self.currentPosition = CGPointMake(self.currentPosition.x + rotationRate.x * 0.02 * -1,
self.currentPosition.y + rotationRate.y * 0.02);
}
else {
self.currentPosition = CGPointMake(self.currentPosition.x + rotationRate.x * 0.02,
self.currentPosition.y + rotationRate.y * 0.02 * -1);
}
}
else {
self.currentPosition = CGPointMake(self.currentPosition.x + rotationRate.y * 0.02,
self.currentPosition.y - rotationRate.x * 0.02 * -1);
}
self.currentPosition = CGPointMake(self.currentPosition.x,
CLAMP(self.currentPosition.y, -M_PI / 2, M_PI / 2));

self.camera.eulerAngles = SCNVector3Make(self.currentPosition.y, self.currentPosition.x, 0);
NYT360EulerAngleCalculationResult result;
result = NYT360DeviceMotionCalculation(self.currentPosition, rotationRate, orientation, self.allowedPanningAxes);
self.currentPosition = result.position;
self.camera.eulerAngles = result.eulerAngles;
}

- (void)setAllowedPanningAxes:(NYT360PanningAxis)allowedPanningAxes {
// TODO: [jaredsinclair] Consider adding an animated version of this method.
_allowedPanningAxes = allowedPanningAxes;
NYT360EulerAngleCalculationResult result = NYT360UpdatedPositionAndAnglesForAllowedAxes(self.currentPosition, allowedPanningAxes);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is it worth only running the remainder of this method if allowedPanningAxes != _allowedPanningAxes?

self.currentPosition = result.position;
self.camera.eulerAngles = result.eulerAngles;
}

- (void)handlePan:(UIPanGestureRecognizer *)recognizer {
Expand All @@ -99,13 +92,9 @@ - (void)handlePan:(UIPanGestureRecognizer *)recognizer {
self.rotateCurrent = point;
self.rotateDelta = subtractPoints(self.rotateStart, self.rotateCurrent);
self.rotateStart = self.rotateCurrent;

CGPoint position = CGPointMake(self.currentPosition.x + 2 * M_PI * self.rotateDelta.x / self.view.frame.size.width * 0.5,
self.currentPosition.y + 2 * M_PI * self.rotateDelta.y / self.view.frame.size.height * 0.4);
position.y = CLAMP(position.y, -M_PI / 2, M_PI / 2);
self.currentPosition = position;

self.camera.eulerAngles = SCNVector3Make(self.currentPosition.y, self.currentPosition.x, 0);
NYT360EulerAngleCalculationResult result = NYT360PanGestureChangeCalculation(self.currentPosition, self.rotateDelta, self.view.bounds.size, self.allowedPanningAxes);
self.currentPosition = result.position;
self.camera.eulerAngles = result.eulerAngles;
break;
default:
break;
Expand Down
17 changes: 17 additions & 0 deletions Sources/NYT360DataTypes.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
//
// NYT360DataTypes.h
// ios-360-videos
//
// Created by Jared Sinclair on 7/27/16.
// Copyright © 2016 The New York Times Company. All rights reserved.
//

@import Foundation;
@import UIKit;
@import SceneKit;
@import CoreMotion;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not all these @imports are likely required here


typedef NS_OPTIONS(NSInteger, NYT360PanningAxis) {
NYT360PanningAxisHorizontal = 1 << 0,
NYT360PanningAxisVertical = 1 << 1
};
30 changes: 30 additions & 0 deletions Sources/NYT360EulerAngleCalculations.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
//
// NYT360EulerAngleCalculations.h
// ios-360-videos
//
// Created by Jared Sinclair on 7/27/16.
// Copyright © 2016 The New York Times Company. All rights reserved.
//

@import Foundation;
@import UIKit;
@import SceneKit;
@import CoreMotion;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not all these @imports are likely required here


#import "NYT360DataTypes.h"

#pragma mark - Data Types

struct NYT360EulerAngleCalculationResult {
CGPoint position;
SCNVector3 eulerAngles;
};
typedef struct NYT360EulerAngleCalculationResult NYT360EulerAngleCalculationResult;

#pragma mark - Calculations

NYT360EulerAngleCalculationResult NYT360UpdatedPositionAndAnglesForAllowedAxes(CGPoint position, NYT360PanningAxis allowedPanningAxes);

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

NYT360EulerAngleCalculationResult NYT360PanGestureChangeCalculation(CGPoint position, CGPoint rotateDelta, CGSize viewSize, NYT360PanningAxis allowedPanningAxes);
93 changes: 93 additions & 0 deletions Sources/NYT360EulerAngleCalculations.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
//
// NYT360EulerAngleCalculations.m
// ios-360-videos
//
// Created by Jared Sinclair on 7/27/16.
// Copyright © 2016 The New York Times Company. All rights reserved.
//

#import "NYT360EulerAngleCalculations.h"

#pragma mark - Constants

static CGFloat NYT360EulerAngleCalculationRotationRateDampingFactor = 0.02;

#pragma mark - Inline Functions

static inline CGFloat NYT360Clamp(CGFloat x, CGFloat low, CGFloat high) {
return (((x) > (high)) ? (high) : (((x) < (low)) ? (low) : (x)));
}

static inline NYT360EulerAngleCalculationResult NYT360EulerAngleCalculationResultMake(CGPoint position, SCNVector3 eulerAngles) {
NYT360EulerAngleCalculationResult result;
result.position = position;
result.eulerAngles = eulerAngles;
return result;
}

static inline CGPoint NYT360AdjustPositionForAllowedAxes(CGPoint position, NYT360PanningAxis allowedPanningAxes) {
BOOL suppressXaxis = (allowedPanningAxes & NYT360PanningAxisHorizontal) == 0;
BOOL suppressYaxis = (allowedPanningAxes & NYT360PanningAxisVertical) == 0;
if (suppressXaxis) {
position.x = 0;
}
if (suppressYaxis) {
position.y = 0;
}
return position;
}

#pragma mark - Calculations

NYT360EulerAngleCalculationResult NYT360UpdatedPositionAndAnglesForAllowedAxes(CGPoint position, NYT360PanningAxis allowedPanningAxes) {
position = NYT360AdjustPositionForAllowedAxes(position, allowedPanningAxes);
SCNVector3 eulerAngles = SCNVector3Make(position.y, position.x, 0);
return NYT360EulerAngleCalculationResultMake(position, eulerAngles);
}

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

CGFloat damping = NYT360EulerAngleCalculationRotationRateDampingFactor;

// TODO: [thiago] I think this can be simplified later
if (UIInterfaceOrientationIsLandscape(orientation)) {
if (orientation == UIInterfaceOrientationLandscapeLeft) {
position = CGPointMake(position.x + rotationRate.x * damping * -1,
position.y + rotationRate.y * damping);
}
else {
position = CGPointMake(position.x + rotationRate.x * damping,
position.y + rotationRate.y * damping * -1);
}
}
else {
position = CGPointMake(position.x + rotationRate.y * damping,
position.y - rotationRate.x * damping * -1);
}
position = CGPointMake(position.x,
NYT360Clamp(position.y, -M_PI / 2, M_PI / 2));

// Zero-out these values here rather than above, since that would over-
// complicate the if/else logic or require unreadable numbers of ternary
// operators.
position = NYT360AdjustPositionForAllowedAxes(position, allowedPanningAxes);

SCNVector3 eulerAngles = SCNVector3Make(position.y, position.x, 0);

return NYT360EulerAngleCalculationResultMake(position, eulerAngles);
}

NYT360EulerAngleCalculationResult NYT360PanGestureChangeCalculation(CGPoint position, CGPoint rotateDelta, CGSize viewSize, NYT360PanningAxis allowedPanningAxes) {

// TODO: [jaredsinclair] Consider adding constants for the multipliers
// TODO: [jaredsinclair] Find out why the y multiplier is 0.4 and not 0.5
position = CGPointMake(position.x + 2 * M_PI * rotateDelta.x / viewSize.width * 0.5,
position.y + 2 * M_PI * rotateDelta.y / viewSize.height * 0.4);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm curious why the y component uses a 0.4 multiplier instead of 0.5.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

when we were testing the movements this speed was discomforting on the y axis

position.y = NYT360Clamp(position.y, -M_PI / 2, M_PI / 2);

position = NYT360AdjustPositionForAllowedAxes(position, allowedPanningAxes);

SCNVector3 eulerAngles = SCNVector3Make(position.y, position.x, 0);

return NYT360EulerAngleCalculationResultMake(position, eulerAngles);
}
2 changes: 2 additions & 0 deletions Sources/Three60_Player.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,5 @@ FOUNDATION_EXPORT const unsigned char Three60_PlayerVersionString[];
// In this header, you should import all the public headers of your framework using statements like #import <Three60_Player_iOS/PublicHeader.h>

#import <Three60_Player/NYT360ViewController.h>
#import <Three60_Player/NYT360DataTypes.h>
#import <Three60_Player/NYT360EulerAngleCalculations.h>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does NYT360EulerAngleCalculations.h actually need to be made public in our umbrella header, or is it a private implementation detail?

76 changes: 76 additions & 0 deletions Three60_PlayerTests/NYT360EulerAngleCalculationsTests.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
//
// NYT360EulerAngleCalculationsTests.m
// ios-360-videos
//
// Created by Jared Sinclair on 7/27/16.
// Copyright © 2016 The New York Times Company. All rights reserved.
//

@import XCTest;
@import CoreMotion;
@import Three60_Player;

@interface NYT360EulerAngleCalculationsTests : XCTestCase

@end

@implementation NYT360EulerAngleCalculationsTests

- (void)testUpdateFunctionShouldZeroOutDisallowedYAxis {
CGPoint position = CGPointMake(100, 100);
NYT360EulerAngleCalculationResult result = NYT360UpdatedPositionAndAnglesForAllowedAxes(position, NYT360PanningAxisHorizontal);
XCTAssertEqual(result.position.x, 100);
XCTAssertEqual(result.position.y, 0);
}

- (void)testUpdateFunctionShouldZeroOutDisallowedXAxis {
CGPoint position = CGPointMake(100, 100);
NYT360EulerAngleCalculationResult result = NYT360UpdatedPositionAndAnglesForAllowedAxes(position, NYT360PanningAxisVertical);
XCTAssertEqual(result.position.x, 0);
XCTAssertEqual(result.position.y, 100);
}

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

- (void)testDeviceMotionFunctionShouldZeroOutDisallowedXAxis {
CGPoint position = CGPointMake(100, 100);
CMRotationRate rate;
rate.x = 1000;
rate.y = -1000;
rate.z = 10;
UIInterfaceOrientation orientation = UIInterfaceOrientationLandscapeLeft;
NYT360EulerAngleCalculationResult result = NYT360DeviceMotionCalculation(position, rate, orientation, NYT360PanningAxisVertical);
XCTAssertEqual(result.position.x, 0);
XCTAssertNotEqual(result.position.y, 0);
}

- (void)testPanGestureChangeFunctionShouldZeroOutDisallowedYAxis {
CGPoint position = CGPointMake(100, 100);
CGPoint delta = CGPointMake(1000, -1000);
CGSize viewSize = CGSizeMake(536, 320);
NYT360EulerAngleCalculationResult result = NYT360PanGestureChangeCalculation(position, delta, viewSize, NYT360PanningAxisHorizontal);
XCTAssertNotEqual(result.position.x, 0);
XCTAssertEqual(result.position.y, 0);
}

- (void)testPanGestureChangeFunctionShouldZeroOutDisallowedXAxis {
CGPoint position = CGPointMake(100, 100);
CGPoint delta = CGPointMake(1000, -1000);
CGSize viewSize = CGSizeMake(536, 320);
NYT360EulerAngleCalculationResult result = NYT360PanGestureChangeCalculation(position, delta, viewSize, NYT360PanningAxisVertical);
XCTAssertEqual(result.position.x, 0);
XCTAssertNotEqual(result.position.y, 0);
}


@end
Loading