Activity › Forums › Adobe After Effects Expressions › Expression to animate Along Motion Path
-
Expression to animate Along Motion Path
Posted by Mike Sevigny on March 6, 2026 at 3:02 pmIs there an expression that mirrors the behavior of Auto-Orient > Along Motion Path? It appears to be more than just lookAt(position, next potision + velocity).
Is there one that mimics the behavior exactly?
Mike
Mike Sevigny replied 2 months ago 2 Members · 4 Replies -
4 Replies
-
Dan Ebberts
March 6, 2026 at 3:34 pm2D? If so, try this:
p = position;
n = p.numKeys;
if (n > 0){
if (time < p.key(1).time){
v = p.velocityAtTime(p.key(1).time);
}else if (time > p.key(n).time){
v = p.velocityAtTime(p.key(n).time);
}else{
v = p.velocity;
}
}else{
v = p.velocity;
}
radiansToDegrees(Math.atan2(v[1],v[0])) -
Mike Sevigny
March 6, 2026 at 4:01 pmThanks Dan,
I should have mentioned, it’s actually 3D (orientation) I’m looking for. This 2D version works similarly to what I have in 3D. It has difficulty when the motion is ‘paused’ mid timeline, or if the motion starts later in the timeline, or if the motion ends before the end of the timeline.
Here’s the butchered version I have so far:
var eps = 0.0001;
var step = thisComp.frameDuration;
var deltaT = step;
function len(v){return Math.sqrt(v[0]*v[0] + v[1]*v[1] + v[2]*v[2]);}
var v = position.velocityAtTime(time);
// --- Case 1: Layer is moving this frame ---
if(len(v) > eps){
lookAt(position, position + v);
// --- Case 2: Layer is stationary ---
}else{
var backwardResult = null;
var forwardResult = null;
var isPaused = false;
// --- Backward search: last moving tangent ---
var tBack = time - step;
while(tBack >= thisComp.displayStartTime){
var p1 = position.valueAtTime(tBack);
var p2 = position.valueAtTime(Math.min(tBack + deltaT, time));
var disp = [p2[0]-p1[0], p2[1]-p1[1], p2[2]-p1[2]];
if(len(disp) > eps){
backwardResult = lookAt(p1, p2);
break;
}
tBack -= step;
}
// --- Forward search: next moving tangent ---
var tFwd = time;
while(tFwd <= thisComp.duration - deltaT){
var p1 = position.valueAtTime(tFwd);
var p2 = position.valueAtTime(tFwd + deltaT);
var disp = [p2[0]-p1[0], p2[1]-p1[1], p2[2]-p1[2]];
if(len(disp) > eps){
forwardResult = lookAt(p1, p2);
break;
}
tFwd += step;
}
// --- Check if we are in a motion pause (no motion currently) ---
if(!backwardResult && forwardResult){
// No backward motion found, but forward motion exists
// We are in a motion pause: use forwardResult until motion picks up
result = forwardResult;
} else if(backwardResult && !forwardResult){
// Only backward motion exists (motion ended): hold last moving tangent
result = backwardResult;
} else if(backwardResult && forwardResult){
// Both tangents exist: pick closer in time
var distBack = time - tBack;
var distFwd = tFwd - time;
// Bunch of conditions I added to deal with 'pausing' in certain cases (still not perfect)
if( (((distBack > step) && (distFwd > (step))) && ((distBack > (step*2)) || (distFwd > (step*2))))
|| ((distBack > step*2) && ((distFwd > 0) && (distFwd < step*1.1)) ) ){
result = [0,0,0];
}else if(distBack > step){
result = forwardResult;
// result= [0,0,0];
} else {
result = distBack <= distFwd ? backwardResult : forwardResult;
}
} else {
// No motion anywhere: fallback to default orientation
result = [0,0,0];
}
result;
}I managed to match most of the abrupt turns in the motion but I’m continuing to add conditions to match the behavior of the ‘pausing’. I still can’t seem to get it 100% the same. At this point I feel like I’m making it more complicated than it needs to be.
EDIT: I realize that this expression will be slow, but it’s being turned into keyframes right after so I’m not so concerned about the speed of it.
Mike
-
Mike Sevigny
March 6, 2026 at 5:30 pmHere’s a cleaner expression of the 3D version I replied earlier:
// Auto-Orient along Motion Path //
var eps = 0.0001;
var step = thisComp.frameDuration;
var deltaT = step;
function len(v){return Math.sqrt(v[0]*v[0] + v[1]*v[1] + v[2]*v[2]);}
var v = position.velocityAtTime(time);
// --- Case 1: Layer is moving this frame ---
if(len(v) > eps){
lookAt(position, position + v);
// --- Case 2: Layer is stationary ---
}else{
var backwardResult = null;
var forwardResult = null;
// --- Backward search: last moving tangent ---
var tBack = time - step;
while(tBack >= thisComp.displayStartTime){
var p1 = position.valueAtTime(tBack);
var p2 = position.valueAtTime(Math.min(tBack + deltaT, time));
var disp = [p2[0]-p1[0], p2[1]-p1[1], p2[2]-p1[2]];
if(len(disp) > eps){
backwardResult = lookAt(p1, p2);
break;
}
tBack -= step;
}
// --- Forward search: next moving tangent ---
var tFwd = time;
while(tFwd <= thisComp.duration - deltaT){
var p1 = position.valueAtTime(tFwd);
var p2 = position.valueAtTime(tFwd + deltaT);
var disp = [p2[0]-p1[0], p2[1]-p1[1], p2[2]-p1[2]];
if(len(disp) > eps){
forwardResult = lookAt(p1, p2);
break;
}
tFwd += step;
}
// --- Check if we are in a motion pause (no motion currently) ---
if(!backwardResult && forwardResult){
// No backward motion found, but forward motion exists
result = forwardResult;
} else if(backwardResult && !forwardResult){
// Only backward motion exists (motion ended): hold last moving tangent
result = backwardResult;
} else {
// Fallback to default orientation
result = [0,0,0];
}
result;
I removed some of the unused variables/conditions I was playing with earlier. It works perfectly in a lot of cases, but not always.
-
Mike Sevigny
March 9, 2026 at 3:40 pmHere’s the Final Version of the expression I came up with in case it helps someone.
// ---------------------------------------------------------
// Replicate Auto-Orient > Along Motion Path
// Apply Expression to 3D 'Orientation'
// ---------------------------------------------------------
// FUCNTIONS //
function arraysEqual(a, b, eps){
for(var i=0; i<a.length; i++){
if(Math.abs(a[i]-b[i]) > eps) return false;
}
return true;
}
function betweenEqualKeys(prop, eps){
if(!prop.numKeys || prop.numKeys < 2) return false;
var nearest = prop.nearestKey(time).index;
var prevIndex = (prop.key(nearest).time > time && nearest > 1) ? nearest - 1 : nearest;
var nextIndex = prevIndex + 1;
if(nextIndex > prop.numKeys) return false;
return arraysEqual(prop.key(prevIndex).value, prop.key(nextIndex).value, eps);
}
function len(v){return Math.sqrt(v[0]*v[0] + v[1]*v[1] + v[2]*v[2]);}
// CODE //
var eps = 0.0001; // Tolerance
var step = thisComp.frameDuration/10; // Divide by 10 for better accuracy
var v = position.velocityAtTime(time); // Current velocity
// If Motion Speed meets threshold
if(len(v) > eps){
lookAt(position, position + v);
// If Motion Speed does NOT meet threshold
}else{
result = [0,0,0]; // Default to Zero
// If the current frame is NOT between 2 identical keyframes
// This condition matches the original behavior but may not be desirable
if (!betweenEqualKeys(position, eps)){
// Crawl Forward frame by frame until motion is found
var tFwd = time;
while(tFwd < thisComp.duration){
var p1 = position.valueAtTime(tFwd);
var p2 = position.valueAtTime(tFwd + step);
var disp = [p2[0]-p1[0], p2[1]-p1[1], p2[2]-p1[2]];
if(len(disp) > eps){
result = lookAt(p1, p2); // Found Forward Motion
break;
}
tFwd += step;
}
// If Forward motion was NOT found
if (tFwd >= thisComp.duration){
// Crawl Backward frame by frame until motion is found
var tBack = time - step;
while(tBack >= thisComp.displayStartTime){
var p1 = position.valueAtTime(tBack);
var p2 = position.valueAtTime(Math.min(tBack + step, time));
var disp = [p2[0]-p1[0], p2[1]-p1[1], p2[2]-p1[2]];
if(len(disp) > eps){
result = lookAt(p1, p2); // Found Backward Motion
break;
}
tBack -= step;
}
}
}
result;
}NOTE: this expression has the potential to read every frame in your timeline making it computationally expensive in some cases. Save your project before using it.
I was able to match the behavior of the native Auto-Orient > Along Motion Path very closely. What the expression doesn’t account for is when the native function randomly flips all the axis’ 180 degrees or when it sometimes defaults back to [0,0,0] at the beginning/end of the timeline if there are no keyframes. In that respect, the expression might be better.
I tried using toWorld for the velocity to evaluate the parented motion but things became understandably very slow at that point.
Reply to this Discussion! Login or Sign Up