blob: e4ee2ec1e7185a7139ed571c161408aec528ae2d [file] [log] [blame]
/**
* Copyright 2018 Alibaba Group
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#import "EBGyroManager.h"
#import <CoreMotion/CoreMotion.h>
#import "EBUtility.h"
#define GYRO_UPDATE_INTERVAL 1/60
#define rad2deg(rad) ((rad) * 180 / M_PI)
@interface EBGyroManager ()
@property(nonatomic, strong) CMMotionManager *motionManager;
@property(nonatomic, strong) NSMutableArray<id<EBGyroWatcherProtocol>> *watchers;
@end
@implementation EBGyroManager
+ (void)watchOrientation:(id<EBGyroWatcherProtocol>)watcher {
[[EBGyroManager sharedInstance] watchOrientation:watcher];
}
+ (void)removeOrientation:(id<EBGyroWatcherProtocol>)watcher {
[[EBGyroManager sharedInstance] removeOrientation:watcher];
}
+ (instancetype)sharedInstance {
static EBGyroManager *instance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
instance = [EBGyroManager new];
});
return instance;
}
- (instancetype)init {
if (self = [super init]) {
_watchers = [NSMutableArray new];
}
return self;
}
- (void)startMotionManager {
if (!_motionManager) {
_motionManager = [CMMotionManager new];
_motionManager.gyroUpdateInterval = GYRO_UPDATE_INTERVAL;
[_motionManager startDeviceMotionUpdatesToQueue:[NSOperationQueue new]
withHandler:
^(CMDeviceMotion * _Nullable motion, NSError * _Nullable error) {
if (error) {
return;
}
// Acceleration is user + gravity
CMAcceleration userAccel = motion.userAcceleration;
CMAcceleration gravity = motion.gravity;
CMAcceleration totalAccel;
totalAccel.x = userAccel.x + gravity.x;
totalAccel.y = userAccel.y + gravity.y;
totalAccel.z = userAccel.z + gravity.z;
CMAttitude* attitude = motion.attitude;
// Compose the raw motion data to an intermediate ZXY-based 3x3 rotation
// matrix (R) where [z=attitude.yaw, x=attitude.pitch, y=attitude.roll]
// in the form:
//
// / R[0] R[1] R[2] \
// | R[3] R[4] R[5] |
// \ R[6] R[7] R[8] /
double cX = cos(attitude.pitch);
double cY = cos(attitude.roll);
double cZ = cos(attitude.yaw);
double sX = sin(attitude.pitch);
double sY = sin(attitude.roll);
double sZ = sin(attitude.yaw);
double R[] = {
cZ * cY - sZ * sX * sY,
- cX * sZ,
cY * sZ * sX + cZ * sY,
cY * sZ + cZ * sX * sY,
cZ * cX,
sZ * sY - cZ * cY * sX,
- cX * sY,
sX,
cX * cY
};
// Compute correct, normalized values for DeviceOrientation from
// rotation matrix (R) according to the angle conventions defined in the
// W3C DeviceOrientation specification.
double zRot;
double xRot;
double yRot;
if (R[8] > 0) {
zRot = atan2(-R[1], R[4]);
xRot = asin(R[7]);
yRot = atan2(-R[6], R[8]);
} else if (R[8] < 0) {
zRot = atan2(R[1], -R[4]);
xRot = -asin(R[7]);
xRot += (xRot >= 0) ? -M_PI : M_PI;
yRot = atan2(R[6], -R[8]);
} else {
if (R[6] > 0) {
zRot = atan2(-R[1], R[4]);
xRot = asin(R[7]);
yRot = -M_PI_2;
} else if (R[6] < 0) {
zRot = atan2(R[1], -R[4]);
xRot = -asin(R[7]);
xRot += (xRot >= 0) ? -M_PI : M_PI;
yRot = -M_PI_2;
} else {
zRot = atan2(R[3], R[0]);
xRot = (R[7] > 0) ? M_PI_2 : -M_PI_2;
yRot = 0;
}
}
// Rotation around the Z axis (pointing up. normalized to [0, 360] deg).
double alpha = rad2deg(zRot > 0 ? zRot : (M_PI * 2 + zRot));
// Rotation around the X axis (top to bottom).
double beta = rad2deg(xRot);
// Rotation around the Y axis (side to side).
double gamma = rad2deg(yRot);
__weak typeof(self) welf = self;
[EBUtility performBlockOnMainThread:^{
NSArray *watchers = [welf.watchers copy];
for (id<EBGyroWatcherProtocol> watcher in watchers) {
[watcher orientaionChanged:alpha beta:beta gamma:gamma];
}
}];
}];
}
}
- (void)watchOrientation:(id<EBGyroWatcherProtocol>)watcher {
[EBUtility performBlockOnMainThread:^{
[_watchers addObject:watcher];
[self startMotionManager];
}];
}
- (void)removeOrientation:(id<EBGyroWatcherProtocol>)watcher {
[EBUtility performBlockOnMainThread:^{
[_watchers removeObject:watcher];
if (_watchers.count == 0) {
[_motionManager stopDeviceMotionUpdates];
_motionManager = nil;
}
}];
}
@end