Rotate, project, anchor

The skull stands. Now it turns, and every feature has to go along – foreshortening and occlusion included. The whole trick is that no feature function knows anything about it.

Between the head shape and the drawn line lie three steps: rotate, project, anchor. The first two are four lines of arithmetic. The third is the Feld – and it is the reason there is not a single sine of a head rotation in gesicht.ts.

Rotate and project

Rotation happens in a fixed order: first yaw about the Y axis, then pitch about X, then roll about Z. That is not a matter of taste – the same three angles in a different order are a different rotation.

It exists in two forms, because it is needed at two orders of magnitude. The point cloud rotates tens of thousands of points per frame: there the drehMatrix is set up once and applied to every point. An anchor, a nose tip, the end of a braid are single points: there drehUm(p, pose) is more convenient.

Both are the same computation: drehUm builds the matrix and applies it. There were once two computation paths side by side here – the matrix multiplied out, drehUm as a chain of three rotations – which had to keep the same order without anything forcing them to. Had they drifted apart, the contour of a turned head (from the cloud) would sit next to its face (from the anchors) – and nobody would have known which of the two was the wrong one.

Then the projection. It is a weak perspective: a scalar s = F/(F−z) with the focal length F = BRENNWEITE = 4.2. Near things get larger, far things smaller, and the foreshortening of a turned face follows from that by itself – nobody computes it.

Two things sit inside those three lines. First the homothety: because F scales with size and all radii do too, size cancels out of s – the cache on the next page builds on that. Second the sign of y: the projection flips it, because the picture axis points downwards. Miss that and you draw pouts (see below).

The field

A Feld is an anchor on the head's surface, resolved for exactly one view. macheFeld(u, v, sicht) samples the surface at three closely spaced places and reads out of them everything a feature needs at that location:

FieldMeaning
x y zthe picture position of the anchor
ex eythe local axes in the picture, unit vectors
fx fythe squash across and along, measured against refU/refV
nrmthe normal as a screen direction
nzits z component: positive means facing the viewer
to(lx, ly, lift)local coordinate → picture point

refU and refV come from macheSicht: the lengths of the same step size on the unrotated surface at the bridge of the nose. This makes fx = 1 mean not "one unit measure" but "as wide as frontally at the bridge of the nose" – a scale every anchor shares. platz() in the chapter Zones computes back with exactly these two factors.

Feature functions draw exclusively in −1…1 and know nothing about the rotation. An eye is a circle at (0, 0) with radius 0.7. That it is narrow, tilted and occluded when the head is turned is handled by the field.

The third parameter of to is the lift: it raises a point off the skin along the normal. That is how an ear stands beside the head instead of on it, and how a headphone sits above the temple.

At the free anchor you can see the essential thing: push it outwards and the transverse arm of the axis cross gets shorter and shorter until it disappears – fx goes towards zero. Not because the feature gets smaller, but because the surface it lies on turns away.

The inverted cheek

An ear is drawn not beside the head but into the cheek, and the far side suddenly passes every visibility test.

Cause: the normal was flipped towards the viewer so that it would "always point forward". Then nz is never negative, the test nz < 0 never fires, and the lift carries features on the back side inwards with a mirrored direction.

Fix: the normal points outwards from the head's centre. macheFeld checks this with a dot product against the position vector and only flips when the cross product pointed inwards. After that nz < 0 honestly means: turned away from the camera.

The pout

Every smile is a pout. And so convincingly so that one searches the curve for the error for a long time instead of the sign.

Cause: the y axis of an anchor points up – it follows latitude towards the crown. The y axis of the canvas points down. Write a mouth curve in canvas direction and send it unchanged through to, and you get it back mirrored.

Fix: every shape function in gesicht.ts computes in canvas direction and flips the sign in its own to – f.to(lx * r, -ly * r). Not in the field: there it would be wrong for the normal and the axes.

When the anchor tips over the edge

Under strong rotation an eye sitting far out travels past the silhouette. Geometrically that is correct: the surface there is turned away, so the eye does not belong in the picture. As a drawing it is a mistake – a face with only one eye does not read as perspective but as a mishap.

sichtbarerAnker pulls the longitude towards the centre in steps of 10 % until the field faces the camera again. The eye then sits narrow at the edge – and that is exactly right. Note the threshold: min = 0.42, not 0. An anchor that is only just still facing forward has an fx near zero, and an eye of zero width is no solution either.

The other direction of the same question is asked by sichtGrenzen: where does a round line – a hairline, a hat brim – meet the silhouette? The search runs from the most strongly facing spot outwards to both sides, and the edge itself is found by bisection. Analytically that would work only for a smooth ellipsoid; kegel, scheitelHoch, beule and kastig turn the ellipsoid into something for which there is no closed form any more.

How much rotation a style tolerates

The DNA stores the full desired rotation, the style limits it. begrenzePose does that deterministically, without randomness – two calls with the same pose give the same pose.

What is interesting is what the file's header comment records about the scribble style of the first press. That one worked with a window instead of an upper bound: yawMin ruled out the full frontal view. The reason was not taste but geometry – both are degenerate special cases. Frontally the normal points at the camera everywhere, the head has no volume left. In profile the local x axis collapses, fx goes towards zero, and only the lift still carries. Rule both out and you do not have to catch either.

The clear style of this edition does not need the window: its heads look at the pointer, and a head that is looking at somebody simply stands almost frontal. Its limits are { yaw: 0.4, pitch: 0.26, roll: 0.14 } – about 23 degrees to the side, no yawMin.

The pose limits are the only place where a style contradicts the geometry. Everything else – foreshortening, occlusion, order – follows. The limits only say which part of the possible this style can draw without it showing.

Glasses as the counter-example

Almost everything in the face is a field feature. Glasses are not – and that is the only place where the style deliberately refuses the anchor.

A lens drawn through f.to hugs the curvature and foreshortens like skin. It then looks painted on, because a spectacle lens is a rigid disc: it does not lie on the face, it stands in front of it. That is why the glasses here are a real figure in head space – two circles in the frontal plane, a bridge, two temples to the ear – rotated and projected like the skull itself. A turned head then foreshortens a lens only by the cosine of the rotation.

At small rotations the difference is barely visible and from about half a radian on it is impossible to miss: on the left the far lens wraps around the cheek and becomes a line, on the right it stays a disc that gets narrower in perspective. This was learned in the first press, and it is the kind of detail nobody can name but everybody sees.