Skip to main content
carlsev
Participant
March 28, 2018
Answered

Scale shape over time based on position

  • March 28, 2018
  • 1 reply
  • 873 views

I'm totally new to expressions and have spent a couple of hours trying to figure this out. Thanks in advance for your assistance.

I have a shape layer (an ellipse). It is meant to represent where a person's eyes are looking on the screen (like in this video: https://youtu.be/TwNNij89qro). The position of the ellipse changes based on when the eyes move from point to point. I have set all the position keyframes manually. I'd like the scale of the ellipse to change based on how long it remains fixed in one location. The longer it remains in one spot, the larger it gets. A 10% increase per frame seems to work well. So, if the ellipse remains in one place for ten frames, it will increase in size to 200%. If it's just five frames, the ellipse grows to 150%. When it moves to a new location, the scale should reset to 100%.

Here's my newbie expression (feel free to laugh). This expression has been placed on the Scale property for the ellipse. Parts of this seem to work on their own, but it's not doing what I would like it to do as a whole:

var prevFrame = position.valueAtTime(time - .0333)

var currentFrame = position.valueAtTime(time);

if (currentFrame = prevFrame) transform.scale +[10, 10];

else [100, 100];

As you'll see, I tried to look at the position of the previous frame, then compare it's position to the current frame. I've calculated one frame to be .033 seconds. The ellipse scales up to 110% but that's it. It starts at 110% and stays that size for all frames. The expression doesn't generate an error, so I'm thinking I might be close. But I'm also guessing there are ten other ways to do this that might be better.

Thanks for looking this over and offering your guidance.

This topic has been closed for replies.
Correct answer stib

If the position property is driven by keyframes you could get the last keyframe's time and calculate the value from the elapsed time since then. Faster than looping through all the frames.

var p = transform.position;

var n = p.nearestKey(time).index; //gets the index of nearest KF

if (p.key(n).time > time){ n-- ;} // the nearest KF could be *after* the current time, if it is choose the previous KF

if (n <= 1){

      [100,100]; //we've ended up back at the first KF

} else {

     var k1 = p.key(n - 1); //the KF before the nearest one

     var k2 = p.key(n); //the nearest key

     // note that arrays can't be directly compared in JS,

     //hence the code below. Add another dimension for 3D:

     if (k1.value[0] === k2.value[0] && k1.value[1] === k2.value[1] ){

           //KFs are the same

           [100, 100];

     } else {

           // grow the layer 10% for each frame after the previous position key.

           var s = 100 + 10 * (time - k2.time)/thisComp.frameDuration;

           [s, s]

     }

}

1 reply

Dan Ebberts
Community Expert
Community Expert
March 28, 2018

There are a couple of key points about expressions that I think you may be missing. When an expression references the value of the property hosting the expression (like you do with transform.scale) the value you get back is the pre-expression value of the property, as if the expression doesn't exist. So an expression can't propagate a value from one frame to the next that way. In fact, there's no way for an expression to pass or store information for use later. Expressions have no memory.

So, what you have to do is set your expression up to re-create the calculations that led up to the current frame. In your case, you'd need to go back in time, probably frame-by-frame, using valueAtTime() to calculate how long ago the position property arrived at its current value. Then use that value to calculate the current scale.

Dan

stib
stibCorrect answer
Inspiring
March 30, 2018

If the position property is driven by keyframes you could get the last keyframe's time and calculate the value from the elapsed time since then. Faster than looping through all the frames.

var p = transform.position;

var n = p.nearestKey(time).index; //gets the index of nearest KF

if (p.key(n).time > time){ n-- ;} // the nearest KF could be *after* the current time, if it is choose the previous KF

if (n <= 1){

      [100,100]; //we've ended up back at the first KF

} else {

     var k1 = p.key(n - 1); //the KF before the nearest one

     var k2 = p.key(n); //the nearest key

     // note that arrays can't be directly compared in JS,

     //hence the code below. Add another dimension for 3D:

     if (k1.value[0] === k2.value[0] && k1.value[1] === k2.value[1] ){

           //KFs are the same

           [100, 100];

     } else {

           // grow the layer 10% for each frame after the previous position key.

           var s = 100 + 10 * (time - k2.time)/thisComp.frameDuration;

           [s, s]

     }

}

carlsev
carlsevAuthor
Participant
March 30, 2018

YES!!!! Thank you stib_at_work! This does exactly what I needed... Works like a charm, as they say.