A sample iOS application using Ogre 3D

Published on .

iOS Ogre

I'm currently working on an iOS demo of MASA LIFE, the AI middleware I'm developping; in fact I'm porting the demo we did for this year's GDC to iOS. It is called PaperArena, and it is a small capture the flag tank game showcasing the kind of AI you can develop with LIFE. This demo was originally developped for Windows using Ogre 3D.

Paperarena

Long story short, while porting LIFE runtime and the core of the PaperArena code to iOS was quite easy and straightforward, I ran into some problem trying to make Ogre use a UIView that I gave him.

No documentation is available but I found two interesting forum threads, here and there. While providing good base information, none gave a final solution.

After a few hours of work I finally got something running and decided to share it, expecting it might help others and hoping to get some feedback, being neither an Ogre3D nor an iOS expert.

The full repository can be browsed, pulled and forked on github.

The sample

Interesting stuffs

The Objective-C side

The app delegate handles the lifetime of the application, it creates the window, the view controller (from an interface builder file) and start the "simulation". The windows is passed to the view controller because it is needed to properly initialize the Ogre renderer.

- (void)applicationDidFinishLaunching:(UIApplication \*)application {
  self.mWindow = [
    [UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]
  ];
  self.mViewController = [
    [ViewController alloc] initWithNibName:@"ViewController" bundle:nil
  ];
  [self.mViewController startWithWindow:self.mWindow];
  [self.mWindow makeKeyAndVisible];
}

The view controller start method initializes the OgreApplication, the C++ instance that encapsulate the Ogre context and all the actual "simulation", in this case.

- (void)startWithWindow:(UIWindow\*) window {
  unsigned int width = self.view.frame.size.width;
  unsigned int height = self.view.frame.size.height;

  mOgreView = [[OgreView alloc] initWithFrame:CGRectMake(0,0,width,height)];

  [self.view addSubview:mOgreView];

  mApplication.start(window, mOgreView, self, width, height);

  // Linking the ogre view to the render window
  mOgreView.mRenderWindow = mApplication.mRenderWindow;

  // Ogre has created an EAGL2ViewController for the provided mOgreView
  // and assigned it as the root view controller of the window
  //
  // Let's first retrieve it
  UIViewController* ogreViewController = window.rootViewController;

  // I want to be the actual root view controller
  window.rootViewController = self;

  // But i want to add a child link with the ogre one
  [self addChildViewController:ogreViewController];

  // add the ogre view as a sub view
  [self.view addSubview:mOgreView];
  [self.view sendSubviewToBack:mOgreView];
}

On line 5, a sub-view is created, that is the one actually used by Ogre. I would have preferred to be able to directly create such view in the interface builder and even get rid of the parent view but it Ogre creates its own view controller for it (which I failed to replace) and I wanted to keep my own.

This view uses the custom class OgreView (.h, .mm) that "mimicks" what Ogre is expected as a view, ie. the not exported EAGL2View (.h, .mm).

On line 18, Ogre's created view controller is retrieved, self then takes back its place as window's root view controller and a parenting link is created between the two view controllers (should help propagating OS events).

Finally, the Ogre view is attached to the main view and sent to the back for other sub-views to be visible.

The C++ side

On the C++ side, the given pointers are provided when creating Ogre's render window.

void OgreApplication::start(
    void* uiWindow,
    void* uiView,
    void* uiViewController,
    unsigned int width,
    unsigned int height) {
  mRoot = new Ogre::Root("", mResourcesRoot + "ogre.cfg");
  m_StaticPluginLoader.load();
  Ogre::NameValuePairList params;
  params["colourDepth"] = "32";
  params["contentScalingFactor"] = "2.0";
  params["FSAA"] = "16";
  params["Video Mode"] =
    Ogre::StringConverter::toString(width) +
    "x" +
    Ogre::StringConverter::toString(height);
  params["externalWindowHandle"] =
    Ogre::StringConverter::toString((unsigned long)uiWindow);
  params["externalViewHandle"] =
    Ogre::StringConverter::toString((unsigned long)uiView);
  params["externalViewController"] =
    Ogre::StringConverter::toString((unsigned long)uiViewController);
  // Initialize w/o creating a renderwindow.
  mRoot->initialise(false, "");
  // Create the window and attach it to the given UI stuffs.
  mRenderWindow = mRoot->createRenderWindow("",width,height,true,&params);
}

And voilà, everything works as expected!

On top of that, this sample application uses CoreMotion for accelerometer/gyroscope control of the camera as well as touch gestures recognizers and more. Once again it's available on github.

I'm sure I've done some bad stuff and that there is a much simpler way of doing all that. Experts of iOS, experts of Ogre, please let me know, I'll update the application accordingly!

If you are going to GDC Europe next week (19/08/13 and 20/08/13), drop by and say hi (booth #170 on the GDC expor floor). If all goes well for me, you should be able to play PaperArena on an iPad!

Update - 2013/08/26

I've updated both the code on github and the post. I was previously setting params["externalViewController"] instead of params["externalViewControllerHandle"] which is the actual good name for the parameters. That was in fact the reason why this piece of code was working.

Furthermore I have discovered that the system doesn't handle well screen orientation when starting in non portrait mode (e.g. when you set the valid orientations from the settings of the app). This needs further investigation.