Algorithm for mixing 2 axis analog input to control a differential motor drive
Here's a solution that doesn't require complicated if/else chains, doesn't reduce the power when moving full forward or rotating in place, and allows for smooth curves and transitions from moving to spinning.

The idea is simple. Assume the (x,y) joystick values are cartesian coordinates on a square plane. Now imagine a smaller square plane rotated 45º inside it.



The joystick coordinates give you a point in the larger square, and the same point superimposed in the smaller square gives you the motor values. You just need to convert coordinates from one square to the other, limiting the new (x,y) values to the sides of the smaller square.

There are many ways to do the conversion. My favorite method is:


Convert the initial (x,y) coordinates to polar coordinates.
Rotate them by 45 degrees.
Convert the polar coordinates back to cartesian.
Rescale the new coordinates to -1.0/+1.0.
Clamp the new values to -1.0/+1.0.


This assumes the initial (x,y) coordinates are in the -1.0/+1.0 range. The side of the inner square will always be equal to l * sqrt(2)/2, so step 4 is just about multiplying the values by sqrt(2).

Here's an example Python implementation.

import math

def steering(x, y):
    # convert to polar
    r = math.hypot(x, y)
    t = math.atan2(y, x)

    # rotate by 45 degrees
    t += math.pi / 4

    # back to cartesian
    left = r * math.cos(t)
    right = r * math.sin(t)

    # rescale the new coords
    left = left * math.sqrt(2)
    right = right * math.sqrt(2)

    # clamp to -1/+1
    left = max(-1, min(left, 1))
    right = max(-1, min(right, 1))

    return left, right


The original idea for this method -- with a much more complicated transformation method -- came from this article.