Tuesday, September 2, 2014

A Cure for the Jiggles

This one's a problem for the big apps, the ones moving gigabytes of imagery and vectors.  You know, the people who pay me.  It looks like this.

I'm... not feeling so good.

What's happening is a floating point precision problem.   If you want to represent the globe all the way from orbit down to nose picking distance, 32 bits of floating point won't do the trick.  Let's look closer.

The Problem

If single precision floating point doesn't crack it for you, why not use 64 bit doubles?  Sure, if OpenGL ES supported them, though it would be kind of a waste.  At some point you have to convert your data to 32 bit floats (or even fixed point, you weirdo).  There's no escape, but there are cheats.

At first blush it's your image tiles and vectors causing the problem.  That's part of it, but don't forget your motion model.  The quaternion controlling your motion can't really run on 32 bit floating point either.

Here's what I did to fix it all.

The Solutions

Let's start with the motion model, what's turning your gestures into movement.  Zoom in too far and things start twitching.  The problem here is the quaternion logic and the matrix math around it.  In WhirlyGlobe-Maply 2.2 it was 32 bit floats.  The fix was to move all this to 64 bit doubles.

Cool picture courtesy: Indiana University

That helped a lot.  Now the motion is nice and smooth.  But things are still jumpy.  It's the data.  As those 32 bit floats get pushed through the OpenGL ES pipeline, they jump around a little.  It's a little different each frame and it adds up to visual insanity.

There's an easy solution to this one, so let's start with the vectors.  For each batch of vectors we define a local origin.  We pass that origin to the renderer as a double precision matrix.  The renderer multiplies the local matrix by the global model/view/projection matrix and then converts down to single precision.  Since the coordinates are local to that origin, nothing gets too big in 32 bit floating point.

Example tiles with spatial origins

You might think we could do the same thing for image tiles.  We almost can, but we don't represent image tiles individually, we batch them together for speed.  Raw, glorious rendering speed.  The solution is similar to vectors, but with a twist.

For a given tile, we look at its size and its center.  We compare it to the big drawables we've already created, looking for a nearby center.  If it's not too far away, we reuse that big drawable, otherwise we create a new one.

Example origins
In that example there may be 100 different tiles of varying resolutions sorted into origin A and origin B drawables simply because the center is close enough.


The center sharing solution works pretty well in practice.  We don't lose too much performance and the jigglies are gone.

50% fewer seizures!

If you're doing closeup work with vectors, be sure to set kMaplyCenter to YES.  The vector tile module does it and the image tiles do their thing by default.  Twitching problems in screen space objects and overlaid views were fixed with similar trickery.

You'll find this all in WhirlyGlobe-Maply 2.3.