/* * 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 #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]; 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> motionClients; motionClients.reserveInitialCapacity(m_deviceMotionClients.computeSize()); for (auto& client : m_deviceMotionClients) motionClients.uncheckedAppend(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> motionClients; motionClients.reserveInitialCapacity(m_deviceMotionClients.computeSize()); for (auto& client : m_deviceMotionClients) motionClients.uncheckedAppend(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> orientationClients; orientationClients.reserveInitialCapacity(m_deviceOrientationClients.computeSize()); for (auto& client : m_deviceOrientationClients) orientationClients.uncheckedAppend(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