342 lines
11 KiB
Plaintext
342 lines
11 KiB
Plaintext
/*
|
|
* Copyright (C) 2010 Apple Inc. All Rights Reserved.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions
|
|
* are met:
|
|
* 1. Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer.
|
|
* 2. Redistributions in binary form must reproduce the above copyright
|
|
* notice, this list of conditions and the following disclaimer in the
|
|
* documentation and/or other materials provided with the distribution.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
|
|
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
|
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
|
|
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
|
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
|
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
|
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
|
|
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
*/
|
|
|
|
#import "config.h"
|
|
#import "WebCoreMotionManager.h"
|
|
|
|
#if PLATFORM(IOS_FAMILY) && ENABLE(DEVICE_ORIENTATION)
|
|
|
|
#import "DeviceMotionClientIOS.h"
|
|
#import "MotionManagerClient.h"
|
|
#import "WebCoreObjCExtras.h"
|
|
#import "WebCoreThreadRun.h"
|
|
#import <CoreLocation/CoreLocation.h>
|
|
#import <objc/objc-runtime.h>
|
|
#import <pal/spi/cocoa/CoreMotionSPI.h>
|
|
#import <wtf/MathExtras.h>
|
|
#import <wtf/SoftLinking.h>
|
|
|
|
// Get CoreLocation classes
|
|
SOFT_LINK_FRAMEWORK(CoreLocation)
|
|
|
|
SOFT_LINK_CLASS(CoreLocation, CLLocationManager)
|
|
SOFT_LINK_CLASS(CoreLocation, CLHeading)
|
|
|
|
// Get CoreMotion classes
|
|
SOFT_LINK_FRAMEWORK(CoreMotion)
|
|
|
|
SOFT_LINK_CLASS(CoreMotion, CMMotionManager)
|
|
SOFT_LINK_CLASS(CoreMotion, CMAccelerometerData)
|
|
SOFT_LINK_CLASS(CoreMotion, CMDeviceMotion)
|
|
|
|
static const double kGravity = 9.80665;
|
|
|
|
@interface WebCoreMotionManager(Private)
|
|
|
|
- (void)initializeOnMainThread;
|
|
- (void)checkClientStatus;
|
|
- (void)update;
|
|
- (void)sendAccelerometerData:(CMAccelerometerData *)newAcceleration;
|
|
- (void)sendMotionData:(CMDeviceMotion *)newMotion withHeading:(CLHeading *)newHeading;
|
|
|
|
@end
|
|
|
|
@implementation WebCoreMotionManager
|
|
|
|
+ (WebCoreMotionManager *)sharedManager
|
|
{
|
|
static WebCoreMotionManager *sharedMotionManager = [[WebCoreMotionManager alloc] init];
|
|
return sharedMotionManager;
|
|
}
|
|
|
|
- (id)init
|
|
{
|
|
self = [super init];
|
|
if (self)
|
|
[self performSelectorOnMainThread:@selector(initializeOnMainThread) withObject:nil waitUntilDone:NO];
|
|
|
|
return self;
|
|
}
|
|
|
|
- (void)dealloc
|
|
{
|
|
if (WebCoreObjCScheduleDeallocateOnMainThread([WebCoreMotionManager class], self))
|
|
return;
|
|
|
|
ASSERT(!m_updateTimer);
|
|
|
|
if (m_headingAvailable)
|
|
[m_locationManager stopUpdatingHeading];
|
|
|
|
if (m_gyroAvailable)
|
|
[m_motionManager stopDeviceMotionUpdates];
|
|
else
|
|
[m_motionManager stopAccelerometerUpdates];
|
|
|
|
[super dealloc];
|
|
}
|
|
|
|
- (void)addMotionClient:(WebCore::MotionManagerClient *)client
|
|
{
|
|
m_deviceMotionClients.add(*client);
|
|
if (m_initialized)
|
|
[self checkClientStatus];
|
|
}
|
|
|
|
- (void)removeMotionClient:(WebCore::MotionManagerClient *)client
|
|
{
|
|
m_deviceMotionClients.remove(*client);
|
|
if (m_initialized)
|
|
[self checkClientStatus];
|
|
}
|
|
|
|
- (void)addOrientationClient:(WebCore::MotionManagerClient *)client
|
|
{
|
|
m_deviceOrientationClients.add(*client);
|
|
if (m_initialized)
|
|
[self checkClientStatus];
|
|
}
|
|
|
|
- (void)removeOrientationClient:(WebCore::MotionManagerClient *)client
|
|
{
|
|
m_deviceOrientationClients.remove(*client);
|
|
if (m_initialized)
|
|
[self checkClientStatus];
|
|
}
|
|
|
|
- (BOOL)gyroAvailable
|
|
{
|
|
return m_gyroAvailable;
|
|
}
|
|
|
|
- (BOOL)headingAvailable
|
|
{
|
|
return m_headingAvailable;
|
|
}
|
|
|
|
- (void)initializeOnMainThread
|
|
{
|
|
ASSERT(!WebThreadIsCurrent());
|
|
|
|
m_motionManager = adoptNS([allocCMMotionManagerInstance() init]);
|
|
|
|
m_gyroAvailable = m_motionManager.get().deviceMotionAvailable;
|
|
|
|
if (m_gyroAvailable)
|
|
[m_motionManager setDeviceMotionUpdateInterval:kMotionUpdateInterval];
|
|
else
|
|
[m_motionManager setAccelerometerUpdateInterval:kMotionUpdateInterval];
|
|
|
|
m_locationManager = adoptNS([allocCLLocationManagerInstance() init]);
|
|
m_headingAvailable = [getCLLocationManagerClass() headingAvailable];
|
|
|
|
m_initialized = YES;
|
|
|
|
[self checkClientStatus];
|
|
}
|
|
|
|
- (void)checkClientStatus
|
|
{
|
|
if (!pthread_main_np()) {
|
|
[self performSelectorOnMainThread:_cmd withObject:nil waitUntilDone:NO];
|
|
return;
|
|
}
|
|
|
|
// Since this method executes on the main thread, it should always be called
|
|
// after the initializeOnMainThread method has run, and hence there should
|
|
// be no chance that m_motionManager has not been created.
|
|
ASSERT(m_motionManager);
|
|
|
|
if (m_deviceMotionClients.computeSize() || m_deviceOrientationClients.computeSize()) {
|
|
if (m_gyroAvailable)
|
|
[m_motionManager startDeviceMotionUpdates];
|
|
else
|
|
[m_motionManager startAccelerometerUpdates];
|
|
|
|
if (m_headingAvailable)
|
|
[m_locationManager startUpdatingHeading];
|
|
|
|
if (!m_updateTimer)
|
|
m_updateTimer = [NSTimer scheduledTimerWithTimeInterval:kMotionUpdateInterval target:self selector:@selector(update) userInfo:nil repeats:YES];
|
|
} else {
|
|
[m_updateTimer invalidate];
|
|
m_updateTimer = nil;
|
|
|
|
if (m_gyroAvailable)
|
|
[m_motionManager stopDeviceMotionUpdates];
|
|
else
|
|
[m_motionManager stopAccelerometerUpdates];
|
|
|
|
if (m_headingAvailable)
|
|
[m_locationManager stopUpdatingHeading];
|
|
}
|
|
}
|
|
|
|
- (void)update
|
|
{
|
|
// It's extremely unlikely that an update happens without an active
|
|
// motion or location manager, but we should guard for this case just in case.
|
|
if (!m_motionManager || !m_locationManager)
|
|
return;
|
|
|
|
// We should, however, guard for the case where the managers return nil data.
|
|
CMDeviceMotion *deviceMotion = [m_motionManager deviceMotion];
|
|
if (m_gyroAvailable && deviceMotion)
|
|
[self sendMotionData:deviceMotion withHeading:[m_locationManager heading]];
|
|
else {
|
|
if (CMAccelerometerData *accelerometerData = [m_motionManager accelerometerData])
|
|
[self sendAccelerometerData:accelerometerData];
|
|
}
|
|
}
|
|
|
|
- (void)sendAccelerometerData:(CMAccelerometerData *)newAcceleration
|
|
{
|
|
WebThreadRun(^{
|
|
CMAcceleration accel = newAcceleration.acceleration;
|
|
|
|
Vector<WeakPtr<WebCore::MotionManagerClient>> motionClients;
|
|
motionClients.reserveInitialCapacity(m_deviceMotionClients.computeSize());
|
|
for (auto& client : m_deviceMotionClients)
|
|
motionClients.uncheckedAppend(makeWeakPtr(&client));
|
|
|
|
for (auto& client : motionClients) {
|
|
if (client)
|
|
client->motionChanged(0, 0, 0, accel.x * kGravity, accel.y * kGravity, accel.z * kGravity, std::nullopt, std::nullopt, std::nullopt);
|
|
}
|
|
});
|
|
}
|
|
|
|
- (void)sendMotionData:(CMDeviceMotion *)newMotion withHeading:(CLHeading *)newHeading
|
|
{
|
|
WebThreadRun(^{
|
|
// Acceleration is user + gravity
|
|
CMAcceleration userAccel = newMotion.userAcceleration;
|
|
CMAcceleration gravity = newMotion.gravity;
|
|
CMAcceleration totalAccel;
|
|
totalAccel.x = userAccel.x + gravity.x;
|
|
totalAccel.y = userAccel.y + gravity.y;
|
|
totalAccel.z = userAccel.z + gravity.z;
|
|
|
|
CMRotationRate rotationRate = newMotion.rotationRate;
|
|
|
|
Vector<WeakPtr<WebCore::MotionManagerClient>> motionClients;
|
|
motionClients.reserveInitialCapacity(m_deviceMotionClients.computeSize());
|
|
for (auto& client : m_deviceMotionClients)
|
|
motionClients.uncheckedAppend(makeWeakPtr(&client));
|
|
|
|
for (auto& client : motionClients) {
|
|
if (client)
|
|
client->motionChanged(userAccel.x * kGravity, userAccel.y * kGravity, userAccel.z * kGravity, totalAccel.x * kGravity, totalAccel.y * kGravity, totalAccel.z * kGravity, rad2deg(rotationRate.x), rad2deg(rotationRate.y), rad2deg(rotationRate.z));
|
|
}
|
|
|
|
CMAttitude* attitude = newMotion.attitude;
|
|
|
|
Vector<WeakPtr<WebCore::MotionManagerClient>> orientationClients;
|
|
orientationClients.reserveInitialCapacity(m_deviceOrientationClients.computeSize());
|
|
for (auto& client : m_deviceOrientationClients)
|
|
orientationClients.uncheckedAppend(makeWeakPtr(&client));
|
|
|
|
// 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);
|
|
|
|
double heading = (m_headingAvailable && newHeading) ? newHeading.magneticHeading : 0;
|
|
double headingAccuracy = (m_headingAvailable && newHeading) ? newHeading.headingAccuracy : -1;
|
|
|
|
for (size_t i = 0; i < orientationClients.size(); ++i) {
|
|
if (orientationClients[i])
|
|
orientationClients[i]->orientationChanged(alpha, beta, gamma, heading, headingAccuracy);
|
|
}
|
|
});
|
|
}
|
|
|
|
@end
|
|
|
|
#endif
|