Saturday, July 18, 2009

The Little Function That Could

KS operates on a couple of design principles. One of those is that all movements should seem natural. This means that they should accelerate and decelerate smoothly; thankfully, Ren'Py has built-in support for so-called "time warp" functions. These are Python functions that, over the course of a movement, receive a number between 0 and 1 that indicates how far along the movement is, and returns an adjusted number in that range that the display system uses to actually determine the new position. In a basic linear move, in and out value are the same, but for more natural moves, the Ren'Py standard is the "ease" type, which works like this:

def _ease_time_warp(x):
    import math
    return .5 - math.cos(math.pi * x) / 2.0

This is a rather simple cosine wave section, normalized so the peaks are at 0 and 1. KS uses this kind of movement for almost everything.

However, only almost; there is a small set of very slow-moving, long-range pans. And if the moves take very long, the above function becomes a bit undesirable; the speed is never constant. What would actually be perfect here is a function that accelerates, then stays constant for a time, then decelerates at the end. So, I set out to write my own.

The problem is, I'm no good at mathematics. With only a vague idea of how this would work, I added bits to my function until it did what I wanted it to do. And the result is... not exactly an example of elegance. To the point where I don't think I actually really understand what it does anymore.

def acdc_warp(x):
    import math
    n = 10.0
    if (x < (1.0 / n)):
        res = (((2.0 / n) * (0.5 - math.cos(math.pi * (x * (n / 2.0))) / 2.0)) / 2.0) * (n / (n - 1.0))
    elif (x > (1.0 - (1.0 / n))):
        res = (((2.0 / n) * (0.5 - math.cos(math.pi * (1.0 - (((x - 1.0) * n) / 2.0))) / 2.0) / 2.0) + (1.0 - (2.0 / n))) * (n / (n - 1.0))
        res = (x - (0.5 / n)) * (n / (n - 1.0))
    return res

100% organically grown code, warts and all. But hey, it works. This governs the movement of all slow pans, like the one over the classroom CG. Frankly, I've come to quite like this disfigured little function. It may be terminally ugly, but you can almost imagine it struggling to do its best every time it's called. And that is quite moé.

— delta