LookAtMe! A 2D lookAt() function in AE Expressions

The lookAt() function in After Effects’ expression language doesn’t work for 2D.

If you’re trying to use it to align the layer to look at a given point, without having to remember all the trigonometry you learned in school, it turns out you should have spent less time smoking behind the shelter sheds, because this is what it does:

When used in 2D, if the looked-at point is below the looking-from point the direction reverses.

There’s a fix for this though, and later I’ll explain how it can be used to build a better Auto-Orient than the one Adobe gave you.

Read more: LookAtMe! A 2D lookAt() function in AE Expressions

It all hinges on the javascript Math.atan2 function. This takes a y value and an x value (in that order, I don’t know why) and gives you the angle from [0 , 0] to that [x, y] point. OMG TOO MUCH MATHS! Don’t worry, there’s copypasting coming up I promise.

So here’s a homebrewed look-at function. To use it simply copy-paste it into the rotation property on the layer you want to do the looking …

function lookAt2D(targetLayer, sourceLayer, targetPt, sourcePt, theTime){
	// targetLayer - required, layer to look at
	// sourceLayer - optional, layer to look from, defaults to this layer
	// targetPt    - optional, a point on the target at which to look, defaults to the anchor pt
	// sourcePt    - optional, a point on the source layer from which to look, defaults to the anchor pt#7bdcb5#7bdcb5
	// theTime     - optional, time to calculate everything, defaults to the current time
	theTime = (typeof theTime !== "undefined" && theTime !== null) ? theTime : time;
	sourceLayer = sourceLayer || thisLayer;
	sourcePt = (typeof sourcePt !== "undefined" && sourcePt !== null) ?
		sourcePt.hasOwnProperty(valueAtTime) ? 
			sourcePt.valueAtTime(theTime): 
			sourcePt :
		sourceLayer.transform.anchorPoint.valueAtTime(theTime);
	targetPt = (typeof targetPt !== "undefined" && targetPt !== null) ?
		targetPt.hasOwnProperty(valueAtTime) ? 
			targetPt.valueAtTime(theTime): 
			targetPt :
		targetLayer.transform.anchorPoint.valueAtTime(theTime);

	const targetPos = fromComp(targetLayer.toComp(targetPt, theTime));
	const sourcePos = fromComp(sourceLayer.toComp(sourcePt, theTime));
	const diff = targetPos - sourcePos;
	const rot = Math.atan2(diff[1], diff[0]);
	return radiansToDegrees(rot)
}

then below it, something like …

lookAt2D(thisComp.layer("red solid")); 

obvs you can choose any layer you like here. Either type the name between the quotes, or select everything inside the brackets and replace by using the pickwhip.

The function lookAt2D() at the very least takes a single layer as its parameter.

lookAt2D(someLayer);

You can also specify:

  • sourceLayer—the layer from which to do the looking (defaults to the current layer),
  • targetPt—the point on the target at which to look (defaults to the anchor point),
  • sourcePt—a point on the source layer from which to look (defaults to the anchor point),
  • time—if you want the calculations to reference the values at another time (defaults to the current time)

so all together it looks like

lookAt2D(targetLayer, sourceLayer, targetPt, sourcePt, theTime);

The function returns a single rotation value, in degrees, which produces this result:

lookAtMe

So, say you wanted to look at the center of a bulge effect on a layer called “bulge adj layer”, and you wanted to calculate the rotation 1 second behind the current time, you’d do:

function lookAt2D(targetLayer, sourceLayer, targetPt, sourcePt, theTime){
 … (you'd copy and paste the function here, I've ommited it for clarity)
} //← end of the copy-pasted function

const targetLayer = thisComp.layer("bulge adj layer");
const targetPt = thisComp.layer("bulge adj layer").effect("Bulge")("Bulge Center");
lookAt2D(targetLayer, null, targetPt, null, time-1);

If you don’t need to define values, just use null to act as a placeholder.

But Wait, there’s more…

A version of the function can be used to replace the rather feeble Auto-orient function in AE, by applying it to look from the past to the future position of a layer.

For 2D layers apply this expression applied to the rotation channel:

function autoOrient(lookForward,lookBack, sourcePoint, sourceLayer, theTime) {
    lookBack = (typeof lookBack !== "undefined" && lookBack !== null) ?
        lookBack :
        thisComp.frameDuration;
    lookForward = (typeof lookForward !== "undefined" && lookForward !== null) ?
        lookForward :
        thisComp.frameDuration;
    theTime = (typeof theTime !== "undefined" && theTime !== null) ?
        theTime :
        time;
    sourcePoint = (typeof sourcePoint !== "undefined" && sourcePoint !== null) ?
        sourcePoint :
        thisLayer.transform.anchorPoint;
    let targetPos = fromComp(toComp(sourcePoint, theTime + lookForward));
    let sourcePos = fromComp(toComp(sourcePoint, theTime - lookBack));
    //check to see if there is any movement
    //if not scan ahead until there is movement,
    //up until the end of the layer+1 frame
    if (length(sourcePos, targetPos) === 0) {
		let futureTargetPos = targetPos;
        let futureLookForward = theTime + lookForward;
        while (length(futureTargetPos, sourcePos) === 0 && futureLookForward <= thisLayer.outPoint) {
            futureLookForward += thisComp.frameDuration;
            futureTargetPos = fromComp(toComp(sourcePoint, futureLookForward));
        }
        targetPos = ease(theTime, lookForward, futureLookForward, targetPos, futureTargetPos); //ease to the advanced rotation
    }
    //now look backward if there is no movement up ahead
    if (length(sourcePos, targetPos) === 0) {
        let pastSourcePos = sourcePos;
        let pastLookBack = theTime - lookBack;
        while (length(pastSourcePos, targetPos) === 0 && pastLookBack >= thisLayer.inPoint) {
            pastLookBack -= thisComp.frameDuration;
            pastSourcePos = fromComp(toComp(sourcePoint, pastLookBack));
        }
        sourcePos = pastSourcePos;
    }
    const diff = targetPos - sourcePos;
    const rot = Math.atan2(diff[1], diff[0]);
    return radiansToDegrees(rot)
}
autoOrient();

The parameters are all optional, you can just use autoOrient() as in the example, but you can also specify:

  • lookBack and lookForward specify the amount of time to scan forward or behind. By setting them to higher values the object will turn smoothly at sharp corners. If you set lookForward higher than lookBack it looks like the layer is skidding. This could be useful. To set the lookForward and lookBack parameters, change the last line to something like:
autoOrient(0.5, 1.23); //values are in seconds.
  • sourcePoint a point to autoOrient around, by default it is the anchor point
  • sourceLayer another layer to drive the auto orientation, by default it is the current layer

You might notice the while() loops in the middle of the code. This will scan ahead whenever there is no motion until it detects motion and pre-align for that, or if that doesn’t work, scan backwards to base the rotation on previous motion. This prevents it getting confused when the layer is starting the movement, as happens on the first keyframe. This expression will line the layer up, before it moves, ready to go as soon as it starts moving, and ease nicely into a rest rotation at the end.

13 comments

  1. How to I specify which layer is the look from and which layer is the look to? Thanks.

    Reply

    1. ditto.
      This (just) Auto-Orients.
      I thought it was a 2D LookAt.
      :O(

      Reply

      1. I’ve updated the post too make it clearer which function to use. Don’t use the last expression if you want to look at something, use the one before. But you will need to specify the layer you want to look at by editing the expression yourself.

        Reply

  2. I’ve updated the post to make it clearer how to use the lookAtMe function as a looker-atter tool. Note that the last expression is for auto-orient, the one you want is before the “but wait there’s more” sub heading

    Reply

  3. I’ve tried this expression on a 2d layer as an alternative to auto-orient. I like the idea of having control over smoothness. I want my shape to move along a path, come to a rest, and then set off again, but I get twitches/jumps in position and rotation coming out of and going into static positions.
    Is there an easy way to fix that, so that it holds the resting position before, setting off again?

    Reply

  4. Elaborating a bit on my previous comment: atan2 takes two arguments (the first is the y, second is x)

    Reply

  5. function lookAt2(p1, p2) {
    delta = p2 – p1;
    A = Math.atan2(delta[1], delta[0]);
    return radiansToDegrees(A) + 90;
    }

    or the one-liner:

    function lookAt2(p1, p2) { d = p2 – p1; A = Math.atan2(d[1], d[0]); return radiansToDegrees(A) + 90; }

    I wonder how Adobe missed this.

    Reply

  6. I’ve used this and it is great on a character head rig. A hat and two eyes orienting towards the face as i drag the top of the head around using nulls and all parented down to a neck and then to a master. It works and i can move the whole structure around and still works…. BUT it took me ages to get right as you have to put the expression in and do the parenting in a specific order which i cannot remember and now i cannot repeat it on another character it wont work for me
    I am new to expressions by the way.

    Reply

  7. Thnx for this. Just a short question. I’m using 3d Element and have a eye with 3 rotation properties. X,Y, and Z rotation. How to link the X and Y rotation to the X and Y of the looking at properties?

    Reply

  8. missed from article due this video on youtube:
    https://youtu.be/EYbn0dhTKbA?t=196

    function lookAtMe(fromPt, toPt){
    d = toPt – fromPt
    if (d[0] == 0){
    A = (d[1]>0)? 90: -90;
    } else {
    A = radiansToDegrees(Math.atan(d[1]/d[0]))
    }
    return 90 + ((d[0]>0)?A: 180+A);
    }

    var toPt = thisComp.layer(“Null 25”).transform.position;
    var fromPt = transform.position;

    lookAtMe(fromPt, toPt)

    Reply

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.