Procedural walking in houdini

I wanted to do something like this really badly in Maya once. Tried in Houdini for fun, and turns out something like this is easy to do and very entertaining!

The first interation was super simple and basically the idea that the position of each leg snapped to some rounded position of the leg.

The awesome detail about this is that there is no playback happening. This all happens on a static timeline in real time.

The logic now takes ground geo into account and uses frame 1 as a 'pose frame' similar to the capture frame approach. That means that you could keyframe the 'normal' leg positions relative to the body in frame 1, keyframe them and start moving the whole stuff in the other frames.

TODO:
Expose more parameters
If one would animate the base geo, stuff like wiggle or lag could greatly add to the overall animation. As of now, on every frame != 1 the legs will be keyframed, making CHOP effects possible.

import hou
import math


# /////////////////////////////////////// utils
def lerp(t, a, b):
    return a + t * (b - a)


def roundVec(v, fac, seed):
    #seed = random.random()*0.1
    seed = 0.1 * seed - 0.05 * seed
    x = (round(v[0] * fac + seed)) / fac
    y = (round(v[1] * fac + seed)) / fac
    z = (round(v[2] * fac + seed)) / fac
    return hou.Vector3(x, y, z)


def find_nearest(v, node, maxdist=3):
    # TODO probably take the nearest topmost point
    geo = node.displayNode().geometry()
    nearest = [maxdist, v]
    for p in geo.points():
        dist = p.position().distanceTo(v)
        if dist < nearest[0]:
            nearest[0] = dist
            nearest[1] = p.position()
    return nearest[1]


# /////////////////////////////////////// setup
def leg_capturepos(leg):
    p = leg.capture_pos
    t = hou.hmath.buildTranslate(p[0], p[1], p[2])
    return t * PARENT_WT


def place_legs():
    for no, leg in enumerate(LEGS):
        # print leg.name(), no
        p = leg_capturepos(leg)
        leg.restpos = p.extractTranslates()


def move_legs_gnd():
    # distribute_legs()
    place_legs()

    for no, leg in enumerate(LEGS):
        lt = hou.Vector3(leg.parmTuple("t").eval())
        next_p = find_nearest(lt, GROUND)
        dist = next_p.distanceTo(leg.restpos)
        if dist > COMFORT_LENGTH:
            # print 'setting', leg
            next_restp = find_nearest(leg.restpos, GROUND)
            t = hou.hmath.buildTranslate(
                next_restp[0], next_restp[1], next_restp[2])
            leg.setParmTransform(t)
        else:
            pass
            #t = hou.hmath.buildTranslate(next_p[0], next_p[1], next_p[2])
            # leg.setParmTransform(t)


def remove_keyframes():
    if hou.frame() == 1:
        """
        delete all keyframes if rewound
        (@ frame 1)
        """
        for leg in LEGS:
            parm = leg.parmTuple("t")
            parm.deleteAllKeyframes()


def set_keys():
    """
    Set keyframe for all legs
    """
    frame = hou.frame()

    if frame == CAPTURE_FRAME:
        return

    for leg in LEGS:
        pos = hou.Vector3(leg.parmTuple("t").eval())
        kfx = hou.Keyframe()
        kfx.setFrame(frame)
        kfx.setValue(pos[0])
        kfy = hou.Keyframe()
        kfy.setFrame(frame)
        kfy.setValue(pos[1])
        kfz = hou.Keyframe()
        kfz.setFrame(frame)
        kfz.setValue(pos[2])
        tchanx = hou.parm('../' + leg.name() + '/tx')
        tchany = hou.parm('../' + leg.name() + '/ty')
        tchanz = hou.parm('../' + leg.name() + '/tz')
        tchanx.setKeyframe(kfx)
        tchany.setKeyframe(kfy)
        tchanz.setKeyframe(kfz)


# /////////////////////////////////////// globals
THIS_NODE = hou.pwd()
PARENT = THIS_NODE.inputs()[0]
LEGS = hou.node('../l_parents').outputs()
GROUND = hou.node('../ground')
CAPTURE_FRAME = 1
PARENT_WT = PARENT.worldTransform()
COMFORT_LENGTH = 1


def main():
    # This is our 'capture' frame

    if hou.frame() == CAPTURE_FRAME:
        """
        Basically, do nothing in the capture frame.
        Just store the actual leg position in the up vector
        to access it in any other frame as the capture position.
        """
        for leg in LEGS:
            # we use the up vector to store the capture pose per leg
            pos = hou.Vector3(leg.parmTuple("t").eval())
            leg.parmTuple('up').set(pos)
            print leg, pos
            leg.capture_pos = pos
        # move_legs_gnd()
    else:
        # do the dance
        for leg in LEGS:
            p = hou.Vector3(leg.parmTuple("up").eval())
            leg.capture_pos = p
        move_legs_gnd()
        set_keys()


main()