Ideal Binary Blog 

You will find information on all aspects of our work here on our company blog.

Enter your email to hear about our products.

Fresh New Site Design

We've just completed a makeover of the Ideal Binary web site. Aside from changing the CSS and styling, we decided to combine all the blogs into one company blog. This makes browsing the site simpler and makes it easier for us to maintain. Each of the RSS feeds have been combined as well. So if you've followed more than one of our blog RSS feeds, you may see some duplication. The main company blog is now accessible from this link, and the corresponding RSS feed can be obtained here, but all of the old links should continue to work. 

Your feedback is more than welcome, so let us know what you think! As always you can follow us on Twitter for updates, or you can subscribe to our RSS feed or mailing list.

Making An iPhone App – Web Site

When I started the web site for 3D Bookshelf, we had already decided on the graphical theme for the app. I had just completed the high-res icon PSD in Photoshop CS 4 along with all of the book covers, so I had a lot of content to play with. The first step was to plan the layout and determine what bits were going to be dynamic. Once this was done, I started setting things up in Photoshop using 960.gs.

960.gs

Web development can be a lot of fun, but it can be incredibly frustrating, especially when you come face to face with the harsh reality that is browser compatibility (or the lack of it). Things have gotten better over the years, but bugs and inconsistencies pop up from time to time. When it comes to setting up layouts that I know will work everywhere (I hope), I tend to use 960.gs. My technical lead (thanks Franklin!) at a previous job introduced me to 960.gs and I've been using it ever since.

The makers of 960.gs provide a PSD template (along with templates for other tools) with guides so you can design your site cleanly in Photoshop and then extract the pixel perfect sections which slot nicely into the html layout.

Below is a section of the 3D Bookshelf site with the guides visible, along with the markup to position the 'Features' text.

websitesampleguides

<div class="features">
                
   <div class="grid_4 prefix_11">
      <h1>Features</h1>
      <p>Leaf through each ...</p>
   </div>
                
   <div class="clear">
</div>

In the markup above you can see I'm enclosing the Features text inside a div with a class set to "grid_4 prefix_11". This means the text will be displayed on the 12th column and the content will span 4 columns. In total there are 16 columns in this particular layout. The background image is applied using the features css class.

Adding Dynamic Content

Various sections of the site are replaceable. For example, the 'Coming Soon!' banner will be turned off and the "Available on the App Store" will be turned on as soon as 3D Bookshelf goes live. In addition to this, there are two videos intended to be available on the site, and these will be played using jqvideobox_1.00, a JQuery plugin. JQuery is a remarkable javascript library. If you're doing web development, don't leave home without it.

We're also hoping to run a small competition once the app has been released and the competition rules are displayed using FancyBox, another JQuery plugin. The competition itself will be managed by a simple twitter competition service we'll host on our own VPS. There are hooks in the 3D Bookshelf site to query the competition service for the number of participants so far, along with a list of latest participants (and their twitter icons). Twitter competitions seem to be all the rage at the moment.

Screenshots

As we get closer to launch, we'll release more screenshots on the site. We've only got one up there at the moment, the splash screen.

As always you can find out more about updates to the above as and when they happen by following me on twitter or subscribing to my RSS feed.

Making An iPhone App – Content And Icon

3D Bookshelf is our second independent app for iPhone and iPod touch. When we started the project, we made a very conscious decision to spend a lot of time getting the look of the app just right and that includes the icon.

Although both myself and Kevin started out as animators and graphics artists, producing games in our early teens, we've pretty much focused on writing code for the last 14 years. So, we've had to re-learn a lot of the design trends and also the current tools, such as Photoshop CS 4 and Blender.

We brought all of this to bear on 3D Bookshelf. Aside from the book texts, the content production for the app can be broken down in to four categories.

1. 3D Content

As I mentioned in an earlier post, after some initial experimentation, we chose to write a real-time 3D book engine to generate the 3D book mesh so this meant we had no 3D content requirements specifically for the books. In the end, we only required one 3D model to be produced, the 3D bookshelf itself. This 3D model was produced in Blender and exported using our UtopiaGL exporter. The bookshelf model appears on the main menu screen where you can choose a book to read from the bookshelf.

2. Textures

Each book has a book cover texture. These were produced with Photoshop CS 4. In addition to this, the bookshelf model required a wood texture. This was generated using Filter Forge and post processed in Photoshop. You can see a selection of some of the front covers below.

bookcoversample

3. Presentation Graphics

Since the central image in the application is a bookshelf, we decided to go with a wooden theme for the app. As I'm sure you know, this is not unusual for iPhone apps. All backgrounds, including the splash screen, were produced using a wooden background. Again, these were produced with Photoshop and Filter Forge.

4. Icon

For the icon we continued with the wooden theme. But the focus here is on a 3D book image. This was produced entirely in Photoshop using shapes, gradients and effects. The icon was modeled at 1024x1024 and it has quite a bit of nice detail up close. It also scales down to 57x57 very nicely. Below, you can see two sections of the icon up close. When this is reduced to 57x57 all of this detail disappears. But it can still be used for other promotional requirements, such as the web page and Google ads, for example.

papersample bindingsample

As I mentioned before, we're currently adding polish to the app so things may change. It's an exciting time and although we want to get the app out, we're determined to keep the quality level as high as we can get it.

iPhone Content Creation with Blender – Part 2

This is part 2 in a series of posts describing how to extend Blender to fit with your own content production process, specifically with regard to producing content for iPhone.

The 2.5 release of Blender is just around the corner. From the looks of the feature improvements already described on the Blender site we can expect some substantial changes to almost every aspect of the application. One significant area is the UI, in particular scripting updates to the UI.

What I'll describe here is relevant to scripting a UI for an exporter using the 2.49.2 release of Blender. Below is a snapshot of the UtopiaGL exporter in Blender.

ExporterUI

One of the things that bugged me about setting up a UI for the UtopiaGL exporter was that Blender exposes a pretty low-level API for this purpose. This means that you end up dealing with absolute coordinates when positioning textboxes and buttons and so on. From looking at some of the existing Blender exporter plugins, the following type of UI code is not uncommon.

def draw_gui():
    ... # globals removed to save space!

    # Title
    glClear(GL_COLOR_BUFFER_BIT)
    glRasterPos2d(10, 290)
    Text("UtopiaGL .model Export")

    # VNormals / UVs / VColors / VWeights
    Label( "Properties To Export", 430, 190, 120, 20 )

    g_toggle_outputvnormals = Toggle("Vertex Normals", EVENT_NOEVENT, 430, 160, 100, 20, g_toggle_outputvnormals.val, "Output Vertex Normals" )
    g_toggle_outputuvs = Toggle("Vertex UVs", EVENT_NOEVENT, 540, 160, 100, 20, g_toggle_outputuvs.val, "Output Vertex UV Coordinates" )
    g_toggle_outputvcolors = Toggle("Vertex Colors", EVENT_NOEVENT, 430, 130, 100, 20, g_toggle_outputvcolors.val, "Output Vertex Colors" )
    g_toggle_outputvweights = Toggle("Vertex Weights", EVENT_NOEVENT, 540, 130, 100, 20, g_toggle_outputvweights.val, "Output Vertex Weights" )

    Label( "Faces", 430, 90, 80, 20 )
    g_menu_facewinding = Menu("Face Winding %t|Counter-Clockwise %x1|Clockwise %x2|", EVENT_NOEVENT, 430, 60, 150, 20, g_menu_facewinding.val, "Face winding to use" )

    # Content Root / Model File

    Label( "Content Root Path", 10, 240, 80, 20 )
    g_content_root = String("", EVENT_NOEVENT, 10, 210, 300, 20, g_content_root.val, 255, "Content root path")
    Button( "Browse", EVENT_CHOOSE_CONTENT_ROOT, 310, 210, 80, 20 )

    Label( "Model File", 10, 180, 80, 20 )
    g_filename = String("", EVENT_NOEVENT, 10, 150, 300, 20, g_filename.val, 255, "Model file to save")
    Button( "Browse", EVENT_CHOOSE_FILENAME, 310, 150, 80, 20 )

    g_toggle_outputshaders = Toggle("Output Shaders/Skins", EVENT_NOEVENT, 10, 120, 130, 20, g_toggle_outputshaders.val, "Output Shader and Skin files" )

    # Log / Log Level

    Label( "Logging", 10, 90, 80, 20 )
    g_toggle_outputtolog = Toggle("Output Log", EVENT_NOEVENT, 10, 60, 80, 20, g_toggle_outputtolog.val, "Output export progress to log file" )
    Label( "Log Level", 160, 60, 80, 20 )
    g_integer_loglevel = Menu("Log Level %t|Debug %x1|Info %x2|Warning %x3|Error %x4|Critical %x5", EVENT_NOEVENT, 230, 60, 80, 20, g_integer_loglevel.val, "Logging Level to use" )

    # Export / Exit
                
    Button( "Export", EVENT_SAVE_MODEL, 10, 10, 80, 20 )
    Button( "Exit", EVENT_EXIT ,100, 10, 80, 20 )

The above approach works and if your UI is simple and not subject to change you should be fine to implement a UI like this. If on the other hand, you plan to iteratively extend the exporter as and when new requirements appear, you'll probably want something a little more dynamic. For example, inserting a button in the middle of a UI implemented like this means calculating and changing the coordinates of all other UI elements in the surrounding area.

Silverlight and WPF have various types of Panel controls that makes insertion and removal of controls in a layout very easy. I didn't have time to implement a complete panel control, so instead I implemented a Cursor object that allows me to output UI elements at the current location of the Cursor.

class Cursor:
        """A Cursor object, use to maintain context of where to insert UI elements."""

        def __init__(self):
                self.x = 10
                self.y = 10
                self.width = 80
                self.height = 20
                self.virticalpad = 10
                self.horizontalpad = 10

        def set_x(self, newx):
                self.x = newx
        
        def set_y(self, newy):
                self.y = newy
        
        def set_width(self, newwidth):
                self.width = newwidth
        
        def set_height(self, newheight):
                self.height = newheight

        def set_virtical_pad(self, newvirticalpad):
                self.virticalpad = newvirticalpad
        
        def set_horizontal_pad(self, newhorizontalpad):
                self.horizontalpad = newhorizontalpad

        def move_up(self):
                self.y = self.y + (self.height + self.virticalpad)

        def move_down(self):
                self.y = self.y - (self.height + self.virticalpad)

        def offset_up(self, up):
                self.y = self.y + up

        def offset_down(self, down):
                self.y = self.y - down

        def move_left(self):
                self.x = self.x - (self.width + self.horizontalpad)

        def move_right(self):
                self.x = self.x + (self.width + self.horizontalpad)

        def offset_left(self, left):
                self.x = self.x - left

        def offset_right(self, right):
                self.x = self.x + right

        def Button(self, title, handle):
                Button(title, handle, self.x, self.y, self.width, self.height)

        def Toggle(self, title, handle, value, tip):
                return Toggle(title, handle, self.x, self.y, self.width, self.height, value, tip)
        
        def Label(self, title):
                Label(title, self.x, self.y, self.width, self.height)
        
        def Menu(self, options, handle, value, tip):
                return Menu(options, handle, self.x, self.y, self.width, self.height, value, tip)

        def String(self, text, handle, value, l, tip):
                return String(text, handle, self.x, self.y, self.width, self.height, value, l, tip)

By using the cursor we can convert the hard-coded UI code from above to look more like the following. Note, this code now makes it much easier to add and remove UI elements without the need to update the coordinates of the surrounding elements. It does increase the amount of code a little, but the trade off is probably worth it.

def draw_gui():
        ... # globals removed to save space!

        glClear(GL_COLOR_BUFFER_BIT)

        # Export / Exit

        cursor = Cursor()
        cursor.Button( "Export", EVENT_SAVE_MODEL)
        cursor.move_right()
        cursor.Button( "Exit", EVENT_EXIT)

        # Log / Log Level

        cursor.move_left()
        cursor.move_up()
        cursor.offset_up(20)

        g_toggle_outputtolog = cursor.Toggle("Output Log", EVENT_NOEVENT, g_toggle_outputtolog.val, "Output export progress to log file")
        cursor.offset_right(150)
        cursor.Label( "Log Level" )
        cursor.move_right()
        g_integer_loglevel = cursor.Menu("Log Level %t|Debug %x1|Info %x2|Warning %x3|Error %x4|Critical %x5", EVENT_NOEVENT, g_integer_loglevel.val, "Logging Level to use")
        cursor.set_x(10)
        cursor.move_up()
        cursor.Label( "Logging")

        # Content Root / Model File

        cursor.move_up()
        cursor.set_width(130)
        g_toggle_outputshaders = cursor.Toggle("Output Shaders/Skins", EVENT_NOEVENT, g_toggle_outputshaders.val, "Output Shader and Skin files" )

        cursor.move_up()
        cursor.set_width(300)
        g_filename = cursor.String("", EVENT_NOEVENT, g_filename.val, 255, "Model file to save" )
        cursor.offset_right(300)
        cursor.set_width(80)
        cursor.Button( "Browse", EVENT_CHOOSE_FILENAME )
        cursor.move_up()
        cursor.set_x(10)
        cursor.Label( "Model File")

        cursor.move_up()
        cursor.set_width(300)
        g_content_root = cursor.String("", EVENT_NOEVENT, g_content_root.val, 255, "Content root path" )
        cursor.offset_right(300)
        cursor.set_width(80)
        cursor.Button( "Browse", EVENT_CHOOSE_CONTENT_ROOT)
        cursor.move_up()
        cursor.set_x(10)
        cursor.Label( "Content Root Path")

        # Title

        cursor.move_up()
        cursor.move_up()
        cursor.set_width(300)
        cursor.Label( "UtopiaGL .model Export")

        # VNormals / UVs / VColors / VWeights
        
        cursor.set_x(430)
        cursor.set_y(10)
        cursor.move_up()
        cursor.offset_up(20)
        g_menu_facewinding = cursor.Menu("Face Winding %t|Counter-Clockwise %x1|Clockwise %x2|", EVENT_NOEVENT, g_menu_facewinding.val, "Face winding to use" )
        
        cursor.move_up()
        cursor.Label( "Faces")

        cursor.move_up()
        cursor.offset_up(10)
        cursor.set_width(100)

        g_toggle_outputvcolors = cursor.Toggle("Vertex Colors", EVENT_NOEVENT, g_toggle_outputvcolors.val, "Output Vertex Colors")
        cursor.move_right()
        g_toggle_outputvweights = cursor.Toggle("Vertex Weights", EVENT_NOEVENT, g_toggle_outputvweights.val, "Output Vertex Weights")

        cursor.move_left()
        cursor.move_up()
        g_toggle_outputvnormals = cursor.Toggle("Vertex Normals", EVENT_NOEVENT, g_toggle_outputvnormals.val, "Output Vertex Normals")
        cursor.move_right()
        g_toggle_outputuvs = cursor.Toggle("Vertex UVs", EVENT_NOEVENT, g_toggle_outputuvs.val, "Output Vertex UV Coordinates")

        cursor.move_left()
        cursor.move_up()
        cursor.set_width(120)
        cursor.Label( "Properties To Export")

Here's hoping the improvements made in the 2.5 release make this stuff redundant. But for now, this is a manageable way to implement an exporter UI in Blender to make updates and modifications a little easier.

I didn't get around to writing about the Material to Shader mismatch that I mentioned in the previous post. So, there's definitely going to be a part 3 to this series at a minimum. Hopefully, I'll get to it soon.

iPhone Content Creation with Blender – Part 1

This is part 1 in a series of posts describing how to extend Blender to fit with your own content production process, specifically with regard to producing content for iPhone.

~ 3D Bookshelf ~

3D Bookshelf is the world's first fully 3D eBook Reader. Available now on the AppStore for iPhone & iPod touch!

Efficient content production is vital when it comes to reducing development costs. When I started reviewing our own internal processes and tools, I began to see some big opportunities to optimise our content production process, and at the same time dramatically reduce our costs.

This serves two purposes. Firstly, we save some money up front and satisfy our customers quicker. Second, clients will be more likely to license our system if they know they can also save on content production costs.

The first step towards this goal was to make use of Blender in our content production process.

Blender

Blender is an advanced open source 3D content creation application. It's been around for quite some time, but I've only recently given it a full evaluation. The primary benefits it has to offer over similar commercial packages like 3DS Max and Maya are:

  1. It's free. The commercial alternatives can cost anything between 1000 US dollars to about 3500 US dollars.
  2. It's really easy to use. It makes excellent use of keyboard short-cuts to allow you develop efficient processes.
  3. It's really easy to extend. For example, writing an exporter means creating one python file and adding it to a scripts folder. No C/C++ projects or solutions, no need for Visual Studio etc.
  4. It's feature rich. For example, apart from the standard modeling capabilities, it's integrated with Bullet physics. You can model scenes with water-flows and so on.
  5. It's cross platform. Linux, Mac OSX and Windows.

Extending with Python

The most significant component of our iPhone middleware engine (UtopiaGL) is the rendering engine. It uses a proprietary file format which ensures that the 3D content is both legal and also optimised for the engine. In order to load content produced with Blender into the rendering engine, it must first be exported to our proprietary format.

Writing exporters for Blender is extremely straightforward, so long as you know Python. I hadn't used Python for a long time so I had to do a quick refresher course. Luckily, the tutorial page on the Python site is about all you need to get up and running.

Once I had the shell of my exporter set up, I decide to add full logging support. Python has a feature rich logging system so this was very easy to add. I wrote a simple Log class to abstract away the underlying logging system so I could turn the logging completely off, if required. I also added the logging initialisation and shutdown code to this class.

class Log:
        """My log class."""

        def initialize_logger(self, filepath, outputlog, loglevelindex):
                """Create the logger for the exporter."""
                self.outputlog = outputlog
                if self.outputlog:
                        logfilepath = filepath + '.log'
                        self.logger = logging.getLogger('BlenderUtopiaGLExporter')
                        loglevels = [logging.DEBUG, logging.INFO, logging.WARNING, logging.ERROR, logging.CRITICAL]
                        self.logger.setLevel(loglevels[loglevelindex])
                        self.logfilehandler = logging.FileHandler(logfilepath, 'w')
                        self.logger.addHandler(self.logfilehandler)
                        self.logger.info('Begin')
                        self.logger.info('Exporting to file: <%s>' % filepath)

        def close_logger(self):
                """Close the logfilehandler to ensure the log file is closed."""
                if self.outputlog:
                        self.logger.info('End')
                        self.logger.removeHandler(self.logfilehandler)
                        self.logfilehandler.flush()
                        self.logfilehandler.close()

        def info(self, text):
                if self.outputlog:
                        self.logger.info(text)
   
        def debug(self, text):
                if self.outputlog:
                        self.logger.debug(text)
   
        def warning(self, text):
                if self.outputlog:
                        self.logger.warning(text)
   
        def error(self, text):
                if self.outputlog:
                        self.logger.error(text)
   
        def critical(self, text):
                if self.outputlog:
                        self.logger.critical(text)

        def exception(self, text):
                if self.outputlog:
                        self.logger.exception(text)

Having good logging support can make things easier if you want to be able to support the exporter beyond a small development team.  I was a little surprised not to see this approach being used by any of the exporters supplied with Blender. Most seem to opt for just outputting to the console.

Debugging the Exporter

Logging support will help identify the cause of issues reported by the exporter when it's used in the wild. But you'll want to be able to use the python debugger to track down problems during development and to investigate the root cause of problems identified in the logs. Unfortunately, this isn't as straightforward as it should be, but it is possible.

First, you need to get hold of Winpdb. This is a platform independent python debugger. You can find out more about setting this up at the Winpdb site. On windows, don't forget to add the PYTHONHOME environment variable to your system before you install. Mine is set to point to C:\Python26.

Once you have the python debugger set up, you must load your exporter script into the text editor built into Blender and run with ALT-P (press this while focus is in the text editor window). Add the following line to your script beforehand. This is what kicks off the embedded debugging session.

# To use embedded debugging and specify a hardcoded debug
# session password, use the following..
import rpdb2; rpdb2.start_embedded_debugger("password")

# Or, to use embedded debugging with an interactive password,
# use this instead of the above..
# import rpdb2; rpdb2.start_embedded_debugger_interactive_password()

Once the script has been run using the approach above, you'll be able to attach Winpdb to this session. Run %PYTHONHOME%\Scripts\winpdb and from the File menu select Attach. Enter "password" or what ever you've specified as the password for your debugging session and you'll be up and running in the debugger.

What's Next?

With a logging system in place and the ability to debug issues as they arise, it's possible to progress quickly through the development of python scripts from within Blender.

In Part 2 of this series I'll talk about how to add a UI to a Blender exporter. I'll also talk about the Material to Shader mismatch that must be dealt with when exporting 3D content from Blender to be used by OpenGL.