// Copyright (C) 2002-2008 Nikolaus Gebhardt // Copyright (C) 2008 Redshift Software, Inc. // Copyright (C) 2012 Patryk Nadrowski // This file is part of the "Irrlicht Engine". // For conditions of distribution and use, see copyright notice in irrlicht.h #import "CIrrDeviceiOS.h" #ifdef _IRR_COMPILE_WITH_IOS_DEVICE_ #include "IFileSystem.h" #include "CTimer.h" #include "COGLES2Driver.h" #include "MobileCursorControl.h" #import #import /* Important information */ // The application state events and following methods: IrrlichtDevice::isWindowActive, IrrlichtDevice::isWindowFocused // and IrrlichtDevice::isWindowMinimized works out of box only if you'll use built-in CIrrDelegateiOS, // so _IRR_COMPILE_WITH_IOS_BUILTIN_MAIN_ must be enabled in this case. If you need a custom UIApplicationDelegate you must // handle all application events yourself. #ifdef _IRR_COMPILE_WITH_IOS_BUILTIN_MAIN_ namespace irr { class CIrrDeviceiOS; } @interface HideStatusBarView : UIViewController -(BOOL)prefersStatusBarHidden; @end @implementation HideStatusBarView {} -(BOOL)prefersStatusBarHidden { return YES; } @end /* CIrrDelegateiOS */ @interface CIrrDelegateiOS : NSObject - (void)setDevice:(irr::CIrrDeviceiOS*)device; - (bool)isActive; - (bool)hasFocus; @property (strong, nonatomic) UIWindow* window; @end @implementation CIrrDelegateiOS { irr::CIrrDeviceiOS* Device; bool Active; bool Focus; } - (BOOL)application:(UIApplication*)application didFinishLaunchingWithOptions:(NSDictionary*)options { Device = nil; Active = true; Focus = false; [self performSelectorOnMainThread:@selector(runSTK) withObject:nil waitUntilDone:NO]; return YES; } - (void)applicationWillTerminate:(UIApplication*)application { if (Device != nil) { irr::SEvent ev; ev.EventType = irr::EET_APPLICATION_EVENT; ev.ApplicationEvent.EventType = irr::EAET_WILL_TERMINATE; Device->postEventFromUser(ev); Device->closeDevice(); } } - (void)applicationDidReceiveMemoryWarning:(UIApplication*)application { if (Device != nil) { irr::SEvent ev; ev.EventType = irr::EET_APPLICATION_EVENT; ev.ApplicationEvent.EventType = irr::EAET_MEMORY_WARNING; Device->postEventFromUser(ev); } } - (void)applicationWillResignActive:(UIApplication*)application { if (Device != nil) { Device->clearAllTouchIds(); irr::SEvent ev; ev.EventType = irr::EET_APPLICATION_EVENT; ev.ApplicationEvent.EventType = irr::EAET_WILL_PAUSE; Device->postEventFromUser(ev); } Focus = false; } - (void)applicationDidEnterBackground:(UIApplication*)application { if (Device != nil) { Device->clearAllTouchIds(); irr::SEvent ev; ev.EventType = irr::EET_APPLICATION_EVENT; ev.ApplicationEvent.EventType = irr::EAET_DID_PAUSE; Device->postEventFromUser(ev); } Active = false; } - (void)applicationWillEnterForeground:(UIApplication*)application { if (Device != nil) { Device->clearAllTouchIds(); irr::SEvent ev; ev.EventType = irr::EET_APPLICATION_EVENT; ev.ApplicationEvent.EventType = irr::EAET_WILL_RESUME; Device->postEventFromUser(ev); } Active = true; } - (void)applicationDidBecomeActive:(UIApplication*)application { if (Device != nil) { Device->clearAllTouchIds(); irr::SEvent ev; ev.EventType = irr::EET_APPLICATION_EVENT; ev.ApplicationEvent.EventType = irr::EAET_DID_RESUME; Device->postEventFromUser(ev); } Focus = true; } - (void)orientationChanged:(NSNotification*)note { if (Device == nil) return; UIDevice* device = note.object; switch(device.orientation) { case UIDeviceOrientationLandscapeLeft: Device->setUpsideDown(true); break; case UIDeviceOrientationLandscapeRight: Device->setUpsideDown(false); break; case UIDeviceOrientationPortrait: case UIDeviceOrientationPortraitUpsideDown: break; default: break; }; } - (void)runSTK { [[UIDevice currentDevice] beginGeneratingDeviceOrientationNotifications]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(orientationChanged:) name:UIDeviceOrientationDidChangeNotification object:[UIDevice currentDevice]]; override_default_params_for_mobile(); ios_main(0, {}); // App store may not like this exit(0); } - (void)setDevice:(irr::CIrrDeviceiOS*)device { Device = device; } - (bool)isActive { return Active; } - (bool)hasFocus { return Focus; } @end #endif /* CIrrViewiOS */ @interface CIrrViewiOS : GLKView - (id)initWithFrame:(CGRect)frame forDevice:(irr::CIrrDeviceiOS*)device forContext:(EAGLContext*)eagl_context; - (void)setDevice:(irr::CIrrDeviceiOS*)device; @end @implementation CIrrViewiOS { irr::CIrrDeviceiOS* Device; } - (id)initWithFrame:(CGRect)frame forDevice:(irr::CIrrDeviceiOS*)device forContext:(EAGLContext*)eagl_context { self = [super initWithFrame:(frame) context:(eagl_context)]; if (self) { self.drawableDepthFormat = GLKViewDrawableDepthFormat16; self.multipleTouchEnabled = YES; Device = device; } return self; } - (void)setDevice:(irr::CIrrDeviceiOS*)device { Device = device; } - (void)touchesBegan:(NSSet*)touches withEvent:(UIEvent*)event { if (Device == nil) return; irr::SEvent ev; ev.EventType = irr::EET_TOUCH_INPUT_EVENT; ev.TouchInput.Event = irr::ETIE_PRESSED_DOWN; irr::core::position2d mouse_pos = irr::core::position2d(0, 0); bool simulate_mouse = false; for (UITouch* touch in touches) { ev.TouchInput.ID = Device->getTouchId(touch); CGPoint touchPoint = [touch locationInView:self]; ev.TouchInput.X = touchPoint.x * self.contentScaleFactor; ev.TouchInput.Y = touchPoint.y * self.contentScaleFactor; Device->postEventFromUser(ev); if (ev.TouchInput.ID == 0) { simulate_mouse = true; mouse_pos.X = ev.TouchInput.X; mouse_pos.Y = ev.TouchInput.Y; } } if (simulate_mouse) Device->simulateMouse(ev, mouse_pos); } - (void)touchesMoved:(NSSet*)touches withEvent:(UIEvent*)event { if (Device == nil) return; irr::SEvent ev; ev.EventType = irr::EET_TOUCH_INPUT_EVENT; ev.TouchInput.Event = irr::ETIE_MOVED; irr::core::position2d mouse_pos = irr::core::position2d(0, 0); bool simulate_mouse = false; for (UITouch* touch in touches) { ev.TouchInput.ID = Device->getTouchId(touch); CGPoint touchPoint = [touch locationInView:self]; ev.TouchInput.X = touchPoint.x * self.contentScaleFactor; ev.TouchInput.Y = touchPoint.y * self.contentScaleFactor; Device->postEventFromUser(ev); if (ev.TouchInput.ID == 0) { simulate_mouse = true; mouse_pos.X = ev.TouchInput.X; mouse_pos.Y = ev.TouchInput.Y; } } if (simulate_mouse) Device->simulateMouse(ev, mouse_pos); } - (void)touchesEnded:(NSSet*)touches withEvent:(UIEvent*)event { if (Device == nil) return; irr::SEvent ev; ev.EventType = irr::EET_TOUCH_INPUT_EVENT; ev.TouchInput.Event = irr::ETIE_LEFT_UP; irr::core::position2d mouse_pos = irr::core::position2d(0, 0); bool simulate_mouse = false; for (UITouch* touch in touches) { ev.TouchInput.ID = Device->getTouchId(touch); Device->removeTouchId(touch); CGPoint touchPoint = [touch locationInView:self]; ev.TouchInput.X = touchPoint.x * self.contentScaleFactor; ev.TouchInput.Y = touchPoint.y * self.contentScaleFactor; Device->postEventFromUser(ev); if (ev.TouchInput.ID == 0) { simulate_mouse = true; mouse_pos.X = ev.TouchInput.X; mouse_pos.Y = ev.TouchInput.Y; } } if (simulate_mouse) Device->simulateMouse(ev, mouse_pos); } - (void)touchesCancelled:(NSSet*)touches withEvent:(UIEvent*)event { if (Device == nil) return; irr::SEvent ev; ev.EventType = irr::EET_TOUCH_INPUT_EVENT; ev.TouchInput.Event = irr::ETIE_LEFT_UP; irr::core::position2d mouse_pos = irr::core::position2d(0, 0); bool simulate_mouse = false; for (UITouch* touch in touches) { ev.TouchInput.ID = Device->getTouchId(touch); Device->removeTouchId(touch); CGPoint touchPoint = [touch locationInView:self]; ev.TouchInput.X = touchPoint.x * self.contentScaleFactor; ev.TouchInput.Y = touchPoint.y * self.contentScaleFactor; Device->postEventFromUser(ev); if (ev.TouchInput.ID == 0) { simulate_mouse = true; mouse_pos.X = ev.TouchInput.X; mouse_pos.Y = ev.TouchInput.Y; } } if (simulate_mouse) Device->simulateMouse(ev, mouse_pos); } @end namespace irr { struct SIrrDeviceiOSDataStorage { SIrrDeviceiOSDataStorage() : Window(0), ViewController(0), View(0), MotionManager(0), ReferenceAttitude(0) { MotionManager = [[CMMotionManager alloc] init]; m_eagl_context = 0; } ~SIrrDeviceiOSDataStorage() { [Window release]; [ViewController release]; if (View != nil) [View setDevice:nil]; [View release]; [MotionManager release]; [EAGLContext setCurrentContext:0]; [m_eagl_context release]; } UIWindow* Window; UIViewController* ViewController; CIrrViewiOS* View; CMMotionManager* MotionManager; CMAttitude* ReferenceAttitude; EAGLContext* m_eagl_context; }; CIrrDeviceiOS::CIrrDeviceiOS(const SIrrlichtCreationParameters& params) : CIrrDeviceStub(params), DataStorage(0), Close(false), m_upside_down(false) { #ifdef _DEBUG setDebugName("CIrrDeviceiOS"); #endif #ifdef _IRR_COMPILE_WITH_IOS_BUILTIN_MAIN_ CIrrDelegateiOS* delegate = [UIApplication sharedApplication].delegate; [delegate setDevice:this]; #endif DataStorage = new SIrrDeviceiOSDataStorage(); FileSystem->changeWorkingDirectoryTo([[[NSBundle mainBundle] resourcePath] UTF8String]); if (VideoModeList.getVideoModeCount() == 0) { // Add current screen size CGRect screen_bounds = [[UIScreen mainScreen] nativeBounds]; // nativeBounds is the size in a portrait-up orientation, so reverse width and height core::dimension2du screen_size = { (u32)screen_bounds.size.height, (u32)screen_bounds.size.width }; VideoModeList.addMode(screen_size, 32); VideoModeList.setDesktop(32, screen_size); } createWindow(); createViewAndDriver(); if (!VideoDriver) return; createGUIAndScene(); CursorControl = new gui::MobileCursorControl(); } CIrrDeviceiOS::~CIrrDeviceiOS() { deactivateDeviceMotion(); deactivateGyroscope(); deactivateAccelerometer(); delete static_cast(DataStorage); #ifdef _IRR_COMPILE_WITH_IOS_BUILTIN_MAIN_ CIrrDelegateiOS* delegate = [UIApplication sharedApplication].delegate; [delegate setDevice:nil]; #endif } bool CIrrDeviceiOS::run() { if (!Close) { const CFTimeInterval timeInSeconds = 0.000002; s32 result = 0; do { result = CFRunLoopRunInMode(kCFRunLoopDefaultMode, timeInSeconds, TRUE); } while (result == kCFRunLoopRunHandledSource); os::Timer::tick(); //! Update events SIrrDeviceiOSDataStorage* dataStorage = static_cast(DataStorage); CMMotionManager* motionManager = dataStorage->MotionManager; //! Accelerometer if (motionManager.isAccelerometerActive) { irr::SEvent ev; ev.EventType = irr::EET_ACCELEROMETER_EVENT; ev.AccelerometerEvent.X = motionManager.accelerometerData.acceleration.x * 9.81; ev.AccelerometerEvent.Y = motionManager.accelerometerData.acceleration.y * 9.81; ev.AccelerometerEvent.Z = motionManager.accelerometerData.acceleration.z * 9.81; if (m_upside_down) ev.AccelerometerEvent.Y = -ev.AccelerometerEvent.Y; postEventFromUser(ev); } //! Gyroscope if (motionManager.isGyroActive) { irr::SEvent ev; ev.EventType = irr::EET_GYROSCOPE_EVENT; ev.GyroscopeEvent.X = motionManager.gyroData.rotationRate.x; ev.GyroscopeEvent.Y = motionManager.gyroData.rotationRate.y; ev.GyroscopeEvent.Z = motionManager.gyroData.rotationRate.z; postEventFromUser(ev); } //! Device Motion if (motionManager.isDeviceMotionActive) { CMAttitude* currentAttitude = motionManager.deviceMotion.attitude; CMAttitude* referenceAttitude = dataStorage->ReferenceAttitude; if (referenceAttitude != nil) [currentAttitude multiplyByInverseOfAttitude: referenceAttitude]; else referenceAttitude = motionManager.deviceMotion.attitude; irr::SEvent ev; ev.EventType = irr::EET_DEVICE_MOTION_EVENT; ev.AccelerometerEvent.X = currentAttitude.roll; ev.AccelerometerEvent.Y = currentAttitude.pitch; ev.AccelerometerEvent.Z = currentAttitude.yaw; postEventFromUser(ev); } } return !Close; } void CIrrDeviceiOS::yield() { struct timespec ts = {0,0}; nanosleep(&ts, NULL); } void CIrrDeviceiOS::sleep(u32 timeMs, bool pauseTimer=false) { bool wasStopped = Timer ? Timer->isStopped() : true; struct timespec ts; ts.tv_sec = (time_t) (timeMs / 1000); ts.tv_nsec = (long) (timeMs % 1000) * 1000000; if (pauseTimer && !wasStopped) Timer->stop(); nanosleep(&ts, NULL); if (pauseTimer && !wasStopped) Timer->start(); } void CIrrDeviceiOS::setWindowCaption(const wchar_t* text) { } bool CIrrDeviceiOS::isWindowActive() const { #ifdef _IRR_COMPILE_WITH_IOS_BUILTIN_MAIN_ CIrrDelegateiOS* delegate = [UIApplication sharedApplication].delegate; return [delegate isActive]; #else return false; #endif } bool CIrrDeviceiOS::isWindowFocused() const { #ifdef _IRR_COMPILE_WITH_IOS_BUILTIN_MAIN_ CIrrDelegateiOS* delegate = [UIApplication sharedApplication].delegate; return [delegate hasFocus]; #else return false; #endif } bool CIrrDeviceiOS::isWindowMinimized() const { #ifdef _IRR_COMPILE_WITH_IOS_BUILTIN_MAIN_ CIrrDelegateiOS* delegate = [UIApplication sharedApplication].delegate; return ![delegate isActive]; #else return false; #endif } bool CIrrDeviceiOS::present(video::IImage* image, void * windowId, core::rect* src) { return false; } void CIrrDeviceiOS::closeDevice() { CFRunLoopStop(CFRunLoopGetMain()); Close = true; } void CIrrDeviceiOS::setResizable(bool resize) { } void CIrrDeviceiOS::minimizeWindow() { } void CIrrDeviceiOS::maximizeWindow() { } void CIrrDeviceiOS::restoreWindow() { } bool CIrrDeviceiOS::getWindowPosition(int* x, int* y) { *x = 0; *y = 0; return true; } bool CIrrDeviceiOS::activateAccelerometer(float updateInterval) { bool status = false; SIrrDeviceiOSDataStorage* dataStorage = static_cast(DataStorage); CMMotionManager* motionManager = dataStorage->MotionManager; if (motionManager.isAccelerometerAvailable) { if (!motionManager.isAccelerometerActive) { motionManager.accelerometerUpdateInterval = updateInterval; [motionManager startAccelerometerUpdates]; } status = true; } return status; } bool CIrrDeviceiOS::deactivateAccelerometer() { bool status = false; SIrrDeviceiOSDataStorage* dataStorage = static_cast(DataStorage); CMMotionManager* motionManager = dataStorage->MotionManager; if (motionManager.isAccelerometerAvailable) { if (motionManager.isAccelerometerActive) [motionManager stopAccelerometerUpdates]; status = true; } return status; } bool CIrrDeviceiOS::isAccelerometerActive() { SIrrDeviceiOSDataStorage* dataStorage = static_cast(DataStorage); return (dataStorage->MotionManager.isAccelerometerActive); } bool CIrrDeviceiOS::isAccelerometerAvailable() { SIrrDeviceiOSDataStorage* dataStorage = static_cast(DataStorage); return (dataStorage->MotionManager.isAccelerometerAvailable); } bool CIrrDeviceiOS::activateGyroscope(float updateInterval) { bool status = false; SIrrDeviceiOSDataStorage* dataStorage = static_cast(DataStorage); CMMotionManager* motionManager = dataStorage->MotionManager; if (motionManager.isGyroAvailable) { if (!motionManager.isGyroActive) { motionManager.gyroUpdateInterval = updateInterval; [motionManager startGyroUpdates]; } status = true; } return status; } bool CIrrDeviceiOS::deactivateGyroscope() { bool status = false; SIrrDeviceiOSDataStorage* dataStorage = static_cast(DataStorage); CMMotionManager* motionManager = dataStorage->MotionManager; if (motionManager.isGyroAvailable) { if (motionManager.isGyroActive) [motionManager stopGyroUpdates]; status = true; } return status; } bool CIrrDeviceiOS::isGyroscopeActive() { SIrrDeviceiOSDataStorage* dataStorage = static_cast(DataStorage); return (dataStorage->MotionManager.isGyroActive); } bool CIrrDeviceiOS::isGyroscopeAvailable() { SIrrDeviceiOSDataStorage* dataStorage = static_cast(DataStorage); return (dataStorage->MotionManager.isGyroAvailable); } bool CIrrDeviceiOS::activateDeviceMotion(float updateInterval) { bool status = false; SIrrDeviceiOSDataStorage* dataStorage = static_cast(DataStorage); CMMotionManager* motionManager = dataStorage->MotionManager; if (motionManager.isDeviceMotionAvailable) { if (!motionManager.isDeviceMotionActive) { dataStorage->ReferenceAttitude = nil; motionManager.deviceMotionUpdateInterval = updateInterval; [motionManager startDeviceMotionUpdates]; } status = true; } return status; } bool CIrrDeviceiOS::deactivateDeviceMotion() { bool status = false; SIrrDeviceiOSDataStorage* dataStorage = static_cast(DataStorage); CMMotionManager* motionManager = dataStorage->MotionManager; if (motionManager.isDeviceMotionAvailable) { if (motionManager.isDeviceMotionActive) { [motionManager stopDeviceMotionUpdates]; dataStorage->ReferenceAttitude = nil; } status = true; } return status; } bool CIrrDeviceiOS::isDeviceMotionActive() { SIrrDeviceiOSDataStorage* dataStorage = static_cast(DataStorage); return (dataStorage->MotionManager.isDeviceMotionActive); } bool CIrrDeviceiOS::isDeviceMotionAvailable() { SIrrDeviceiOSDataStorage* dataStorage = static_cast(DataStorage); return (dataStorage->MotionManager.isDeviceMotionAvailable); } E_DEVICE_TYPE CIrrDeviceiOS::getType() const { return EIDT_IOS; } void CIrrDeviceiOS::createWindow() { if (CreationParams.DriverType != video::EDT_NULL) { SIrrDeviceiOSDataStorage* dataStorage = static_cast(DataStorage); dataStorage->Window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; dataStorage->ViewController = [[HideStatusBarView alloc] init]; dataStorage->Window.rootViewController = dataStorage->ViewController; [dataStorage->Window makeKeyAndVisible]; } } void CIrrDeviceiOS::createViewAndDriver() { SIrrDeviceiOSDataStorage* dataStorage = static_cast(DataStorage); switch (CreationParams.DriverType) { case video::EDT_OGLES2: #ifdef _IRR_COMPILE_WITH_OGLES2_ { EAGLRenderingAPI OpenGLESVersion = kEAGLRenderingAPIOpenGLES2; // For IOS we use 64bit only and all 64bit ios devices support GLES3 anyway if (!CreationParams.ForceLegacyDevice) OpenGLESVersion = kEAGLRenderingAPIOpenGLES3; else OpenGLESVersion = kEAGLRenderingAPIOpenGLES2; dataStorage->m_eagl_context = [[EAGLContext alloc] initWithAPI:OpenGLESVersion]; [EAGLContext setCurrentContext:dataStorage->m_eagl_context]; CIrrViewiOS* view = [[CIrrViewiOS alloc] initWithFrame:[[UIScreen mainScreen] bounds] forDevice:this forContext:dataStorage->m_eagl_context]; dataStorage->View = view; view.contentScaleFactor = dataStorage->Window.screen.nativeScale; // This will initialize the default framebuffer, which bind its valus to GL_FRAMEBUFFER_BINDING beginScene(); GLint default_fb = 0; glGetIntegerv(GL_FRAMEBUFFER_BINDING, &default_fb); CreationParams.WindowSize = { (u32)view.drawableWidth, (u32)view.drawableHeight }; [dataStorage->Window addSubview:view]; VideoDriver = new video::COGLES2Driver(CreationParams, FileSystem, this, default_fb); if (!VideoDriver) os::Printer::log("Could not create OpenGL ES 2.x driver.", ELL_ERROR); } #else os::Printer::log("No OpenGL ES 2.x support compiled in.", ELL_ERROR); #endif break; case video::EDT_SOFTWARE: case video::EDT_BURNINGSVIDEO: case video::EDT_DIRECT3D9: case video::EDT_OPENGL: os::Printer::log("This driver is not available in iOS. Try OpenGL ES.", ELL_ERROR); break; case video::EDT_NULL: VideoDriver = video::createNullDriver(FileSystem, CreationParams.WindowSize); break; default: os::Printer::log("Unable to create video driver of unknown type.", ELL_ERROR); break; } dataStorage->ViewController.view = dataStorage->View; } void CIrrDeviceiOS::beginScene() { [static_cast(DataStorage)->View bindDrawable]; } void CIrrDeviceiOS::swapBuffers() { [static_cast(DataStorage)->View display]; } std::string CIrrDeviceiOS::getSystemLanguageCode() { NSString* language = [[NSLocale preferredLanguages] firstObject]; return std::string([language UTF8String]); } } #ifdef _IRR_COMPILE_WITH_IOS_BUILTIN_MAIN_ int main(int argc, char** argv) { int result = UIApplicationMain(argc, argv, 0, NSStringFromClass([CIrrDelegateiOS class])); return result; } #endif #endif