/* * 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 "WebCoreObjCExtras.h" #import "WebCoreThreadRun.h" #import #import #import #import #import // 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]; [m_locationManager release]; if (m_gyroAvailable) [m_motionManager stopDeviceMotionUpdates]; else [m_motionManager stopAccelerometerUpdates]; [m_motionManager release]; [super dealloc]; } - (void)addMotionClient:(WebCore::DeviceMotionClientIOS *)client { m_deviceMotionClients.add(client); if (m_initialized) [self checkClientStatus]; } - (void)removeMotionClient:(WebCore::DeviceMotionClientIOS *)client { m_deviceMotionClients.remove(client); if (m_initialized) [self checkClientStatus]; } - (void)addOrientationClient:(WebCore::DeviceOrientationClientIOS *)client { m_deviceOrientationClients.add(client); if (m_initialized) [self checkClientStatus]; } - (void)removeOrientationClient:(WebCore::DeviceOrientationClientIOS *)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 = [allocCMMotionManagerInstance() init]; m_gyroAvailable = m_motionManager.deviceMotionAvailable; if (m_gyroAvailable) m_motionManager.deviceMotionUpdateInterval = kMotionUpdateInterval; else m_motionManager.accelerometerUpdateInterval = kMotionUpdateInterval; m_locationManager = [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.size() || m_deviceOrientationClients.size()) { 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] retain]; } } else { NSTimer *timer = m_updateTimer; m_updateTimer = nil; [timer invalidate]; [timer release]; 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; for (auto& client : copyToVector(m_deviceMotionClients)) client->motionChanged(0, 0, 0, accel.x * kGravity, accel.y * kGravity, accel.z * kGravity, 0, 0, 0); }); } - (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; for (auto& client : copyToVector(m_deviceMotionClients)) 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; auto orientationClients = copyToVector(m_deviceOrientationClients); // 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) orientationClients[i]->orientationChanged(alpha, beta, gamma, heading, headingAccuracy); }); } @end #endif