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 ExpressionsIt 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:
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.
Doug
How to I specify which layer is the look from and which layer is the look to? Thanks.
Peter D Menich
ditto.
This (just) Auto-Orients.
I thought it was a 2D LookAt.
:O(
stib
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.
Joel
Awesome Thanks!
stib
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
TG
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?
Vlad
Check out atan2
Mind. Blown.
Vlad
Elaborating a bit on my previous comment: atan2 takes two arguments (the first is the y, second is x)
Vlad
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.
Dublin Photo Art by Ró Hackett
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.
Roei T
thanks for this 🙂
Arjen
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?
am0
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)