cutan.compile.gaze

Ambient saccades for a cutout rig’s pupils: a seeded generator (an#99, epic #9 Wave 6).

Pure Python, no renderer. Given an entity’s name, a shot’s duration and its frame rate, produce a step track of pupil offsets — where the eyes rest, and when they jump — that the face solver adds onto the pupil nodes’ x / y beside any authored gaze. Steps, not tweens: at 24 fps a frame is 41.7 ms and a small saccade is 20–200 ms, so a jump between two frames is the honest rendering (the way the compiled blink squash samples at frame times).

The statistics are design values, labelled so: the canonical source (“Eyes Alive”, Lee, Badler & Badler, SIGGRAPH 2002) was unreachable when this was designed, and nothing was transcribed from memory. Fixation lengths are right-skewed with a ~200 ms peak and a long tail, so they are drawn from a gamma clipped to [FIXATION_MIN_S, FIXATION_MAX_S]; amplitudes are mostly small with a rare large jump and a horizontal bias; and a jump above BLINK_COUPLED_AMPLITUDE is moved to the centre of the nearest blink window within BLINK_COUPLING_WINDOW_S when one exists, because gaze-evoked blinks hide the pop.

Seeding follows the blink pattern — a pure function of the entity name — so renaming a character re-seeds its saccades (the recorded blink hazard); the seed is stamped into the compiled scene’s meta beside blink_phases. Integer seeding of random.Random is version-stable.

>>> track = saccade_track("gale", duration=2.0, fps=24)
>>> track[0].time, all(t.time <= 2.0 for t in track)
(0.0, True)
>>> track == saccade_track("gale", duration=2.0, fps=24)  # deterministic
True
>>> track != saccade_track("nora", duration=2.0, fps=24)  # per entity
True

Module Attributes

GAZE_SALT

XOR'd into the entity-name hash so saccades and blinks never share a seed.

Functions

gaze_seed(entity_id)

The generator's seed for an entity — a pure function of its name.

saccade_track(entity_id, *, duration, fps[, ...])

Step keyframes on frame times, seeded by entity_id.

Classes

GazeStep(time, x, y)

The pupils rest at (x, y) (axis units) from time on.

cutan.compile.gaze.GAZE_SALT: int = 27182

XOR’d into the entity-name hash so saccades and blinks never share a seed.

class cutan.compile.gaze.GazeStep(time, x, y)[source]

Bases: object

The pupils rest at (x, y) (axis units) from time on.

cutan.compile.gaze.gaze_seed(entity_id)[source]

The generator’s seed for an entity — a pure function of its name.

>>> gaze_seed("gale") == gaze_seed("gale") and gaze_seed("gale") != gaze_seed("nora")
True
Return type:

int

cutan.compile.gaze.saccade_track(entity_id, *, duration, fps, blink_windows=None, amplitude=1.0)[source]

Step keyframes on frame times, seeded by entity_id.

amplitude scales every jump (0 = the eyes hold centre; the design values assume 1). blink_windows are the entity’s compiled blink windows, for the coupling rule.

Return type:

list[GazeStep]