Chapter 10. Expressions

image

Music is math.

—Michael Sandison and Marcus Eoin (Boards of Canada)

Expressions are cool. You can use them to create amazing procedural effects that would otherwise be impossible (or at least impractical). You can also use them to create complex relationships between various parameters. Unfortunately, many After Effects users are afraid of expressions. Don’t be.

The fact that you’re reading this chapter indicates that you are at least curious about expressions. That’s a good start. By the end of the chapter, you’ll see how expressions can open new doors for you, and, hopefully, you’ll have the confidence to give them a try.

The best way to learn about expressions is to examine working examples to figure out what makes them tick. The examples in this chapter focus on how you can use expressions to create or control effects.

As you work through the examples (don’t be discouraged if you need a couple passes or more to understand it all), please keep in mind that I’m mainly a code guy—not a special effects or motion graphics artist. My examples may not be very visually impressive, but using these same techniques, you’ll be able to create your own dazzling effects.

What Expressions Are

The After Effects expression language is a powerful set of tools with which you can control the behavior of a layer’s properties. Expressions can range in complexity from ridiculously simple to mind-numbingly complicated. At the simple end of the spectrum, you can use expressions to link one property to another or to set a property to a static value. At the other extreme, you can create complex linkages, manipulate time, perform calculations in 3D space, set up tricky procedural animations, and more.

Sometimes you’ll use expressions instead of keyframes (most properties that can be keyframed can be controlled by expressions). In other cases you’ll use expressions to augment the keyframed behavior. For example, you could use keyframes to move a layer along a specific path and then add an expression to add some randomness to the motion.

Close-up: Expressions Have Limitations

image

Although the After Effects expression language presents you with an impressive arsenal of powerful tools, it’s important to understand the limitations of expressions so that you can avoid making assumptions that lead you astray.

• An expression may generally be applied only to a property that can be keyframed, and it can affect only the value of that property. That is, an expression can affect one and only one thing: the value of the property to which it is applied. This means there are no global variables. This also means that although an expression has access to many composition and layer attributes (layer width and height, for example) as well as the values of other properties, it can only read, not change, them.

• Expressions can’t create objects. For example, an expression cannot spawn a new layer, add an effect, create a paint stroke, change a blend mode—the list goes on and on. Remember, if you can’t keyframe it, you can’t create an expression for it.

• Expressions can’t access information about individual mask vertices.

• Expressions can’t access text layer formatting attributes, such as font face, font size, leading, or even the height and width of the text itself.

• Expressions cannot access values they created on previous frames, which means expressions have no memory. If you’ve had a little Flash programming experience, you might expect to be able to increment a value at each frame. Nope. Even though you can access previous values of the property using valueAtTime(), what you get is the pre-expression value (the static value of the property plus the effect of any keyframes). It’s as if the expression didn’t exist. There is no way for an expression to communicate with itself from one frame to the next. Note, however, just to make things more confusing, the postexpression value of a property is available to any other expression, just not the one applied to that property. In fact, the postexpression value is the only value available to expressions applied to other properties. To summarize: An expression has access only to the pre-expression value of the property to which it is applied, and it only has access to the postexpression values for other properties with expressions. It’s confusing at first, but it sinks in eventually.

Creating Expressions

The easiest way to create an expression is to simply Alt-click (Opt-click) the stopwatch of the property where you want the expression to go. After Effects then creates a default expression, adds four new tool icons, changes the color of the property value to red (indicating that the value is determined by an expression), and leaves the expression text highlighted for editing (Figure 10.1).

Figure 10.1. When you create an expression, After Effects creates a default expression with the text highlighted for editing, changes the color of the property value to red, and adds four new tool icons: an enable/disable toggle, a Graph Editor toggle, a pick whip, and an Expression Language menu fly-out.

image

At this point you have a number of options. You can simply start typing, and your text will replace the default expression. Note that while you’re in edit mode, the Enter (Return) key moves you to a new line in the expression (this is how you can create multiline expressions) and leaves you in edit mode.

Another option while the text is highlighted is to paste in the text of an expression that you have copied from a text editor. This is the method I generally use if I’m working on a multiline expression.

Instead of replacing all the default text by typing or pasting, you can click somewhere in the highlighted text to create an edit point for inserting additional text.

Alternatively, you can drag the expression’s pick whip to another property or object (the target can even be in another composition), and After Effects will insert the appropriate text when you let go. Note that if an object or property can be referenced using the pick whip, a rounded rectangle appears around the name as you drag the pick whip over it. If this doesn’t happen, you won’t be able to pick whip it.

Finally, you can also use the Expression Language menu to insert various language elements.

After creating your expression, exit edit mode by clicking somewhere else in the timeline or pressing Enter on the numeric keypad. If your expression text contains an error, After Effects displays an error message, disables the expression, and displays a little yellow warning icon (Figure 10.2). You can temporarily disable an expression by clicking on the enable/disable toggle.

Figure 10.2. If your expression contains an error, After Effects disables the expression, changes the enable/disable toggle to the disabled state, returns the Property value to its normal color, displays an error icon, and displays an error message dialog box.

image

Working with existing expressions is as easy as creating them. Some common operations include

editing. Click in the expression text area to select the entire expression; you now have the same options as when creating a new expression. If your expression consists of multiple lines, you may need to expand the expression editing area to be able to see all (or at least more) of it by positioning the cursor over the line below the expression text until you see a double-ended arrow and then clicking and dragging.

deleting. Simply Alt-click (Opt-click) the property’s stopwatch, or you can delete all the text for the expression and press Enter on the numeric keypad.

exposing. Select a layer in the Timeline and press EE to expose any expressions applied to that layer.

copying. In the Timeline panel, select a layer property containing an expression and choose Edit > Copy Expression Only to copy just the property’s expression. You now can select as many other layers as you’d like and Edit > Paste to paste the expression into the appropriate property of the other layers.

The Language of Expressions

The After Effects expression language is based on a subset of JavaScript. JavaScript is a scripting language used largely for Web page design and includes many features specifically aimed at that task. The JavaScript implementation for expressions includes the core features only. That means there’s a lot about JavaScript that you won’t need to know, but it also means that any JavaScript reference you pick up (and you’re going to need one if you really want to master expressions) is going to have a lot of content that will be of little or no use to you.

The rest of the expression language consists of extensions that Adobe has added specifically for After Effects. This means that in addition to a good JavaScript reference, you’ll also be frequenting Adobe’s After Effects Expression Element Reference. The most up-to-date version of this reference can be found at Adobe’s Help on the Web. The After Effects Help menu will take you there: Help > After Effects Help, or you can go to www.adobe.com/support/aftereffects.

This chapter focuses on working examples rather than the details of JavaScript. The book’s disc, however, contains an abbreviated JavaScript guide, and I recommend that you glance through it before you really dive into the sample expressions discussed here. In addition, I’ll point you to the appropriate sections of that guide as you encounter new JavaScript elements for the first time.

Linking an Effect Parameter to a Property

Here’s the scenario: You want to link an effect to an audio track. Specifically, you want to link the Field of View (FOV) parameter of the Optics Compensation effect to the amplitude of an audio layer. Expressions can’t access audio levels directly, so first you have to use a keyframe assistant (Animation > Keyframe Assistant > Convert Audio to Keyframes) to create a null layer named Audio Amplitude with Slider Controls keyframed for the audio levels of the Left, Right, and Both channels (for a stereo source). Next, you just Alt-click (Opt-click) the stopwatch for the FOV parameter of the Optics Compensation effect and drag the pick whip to the Both Channels Slider property of the Audio Amplitude layer (Figure 10.3). Doing so generates this expression:

thisComp.layer("Audio Amplitude").effect("Both
Channels")("Slider")

Figure 10.3. Select the Both Channels slider with the pick whip to replace the highlighted default expression text.

image

Take a closer look at its syntax: From JavaScript, the After Effects expression language inherits a left-to-right “dot” notation used to separate objects and attributes in a hierarchy. If your expression references a property in a different layer, you first have to identify the composition. You can use thisComp if the other layer happens to be in the same composition (as in this example). Otherwise, you would use comp("other comp name"), with the other composition name in quotes. Next you identify the layer using layer("layer name") and finally, the property, such as effect("effect name")("property name") or possibly transform.rotation.

In addition to objects and properties, the dot notation hierarchy can include references to an object’s attributes and methods. An attribute is just what you would guess: a property of an object, such as a layer’s height or a composition’s duration. In fact, in JavaScript documentation, attributes are actually referred to as properties, but in order to avoid confusion with the layer properties such as Position and Rotation (which existed long before expressions came along), in After Effects documentation (and here) they’re referred to as attributes. For example, each layer has a height attribute that can be referenced this way:

comp("Comp 1").layer("Layer 1").height

Methods are a little harder to grasp. Just think of them as actions or functions associated with an object. You can tell the difference between attributes and methods by the parentheses that follow a method. The parentheses may enclose some comma-separated parameters.

It’s important to note that you don’t have to specify the full path in the dot notation hierarchy if you’re referencing attributes or properties of the layer where the expression resides. If you leave out the comp and layer references, After Effects assumes you mean the layer with the expression. So, for example, if you specify only width, After Effects assumes you mean the width of the layer, not the width of the composition.

Let’s forge ahead. You linked the amplitude of your audio layer to your effect parameter, but suppose you want to increase the effect that the audio level has on the parameter. You can use a little JavaScript math to multiply the value by some amount, like this

Notes

image

If you’re not familiar with JavaScript arithmetic operators (such as the * for multiplication used in this example), you might want to take a look at the “Operators” section of the JavaScript guide on the book’s disc.

thisComp.layer("Audio Amplitude").effect("Both
Channels")("Slider") * 3

Toward the end of the chapter you’ll see a much more complicated and powerful way of linking an effect to audio.

Using a Layer’s Index

A layer’s index attribute can be used as a simple but powerful tool that allows you to create expressions that behave differently depending on where the layer is situated in the layer stack. The index attribute corresponds exactly to the number assigned to the layer in the Timeline window. So, the index for the layer at the top of the stack is 1, and so on.

Time Delay Based on Layer Index

Suppose you keyframed an animation for one layer. Now you want to create a bunch of identical layers, but you want their animations to be delayed by an amount that increases as you move down the layer stack. You also want to rotate each copy by an amount proportional to its position in the layer stack. To do so, you first apply an expression like this to the top layer’s animated properties:

delay = 0.15;
valueAtTime(time - (index-1)*delay)

Then you apply an expression like this to the Rotation property:

offsetAngle = 3;
value +(index-1)*offsetAngle

Finally, duplicate the layer a bunch of times. The animation of each layer will lag behind the layer above it by 0.15 seconds and the rotation of each layer will be 3 degrees more than the layer above (Figure 10.4).

Figure 10.4. Notice how the blaster shot created by each layer lags that of the previous layer and is at a slightly different angle.

image

What’s going on here? In the first expression, the first line defines a JavaScript variable named delay and sets its value to 0.15 seconds. The second line is where all the action is, and it’s packed with new things. For example, notice the use of time. It represents the current composition time, in seconds. In other words, time represents the time at which the expression is currently being evaluated.

Notes

image

If you’re not familiar with JavaScript variables, see the “Variables” section of the JavaScript guide on the accompanying disc.

You use valueAtTime() to access a property’s pre-expression value at some time other than the current comp time (to access the pre-expression value at the current comp time, use value() instead, as in the Rotation expression). The parameter passed to valueAtTime() determines that time:

time – (index-1)*delay

Subtracting 1 from the layer’s index and multiplying that result by the value of the delay variable (0.15) gives the total delay (in seconds) for this layer. Subtracting 1 from index means that the delay will be 0 for the first layer. So, for Layer 1, the total delay is 0, for Layer 2 it is 0.15, for Layer 3 it is 0.30, and so on. You then subtract the total delay from the current comp time. The result of this is that Layer 1’s animation runs as normal (not delayed). Layer 2’s animation lags behind Layer 1 by 0.15 seconds, and so on.

Tip

image

Remember, if you don’t specify a comp and layer when referencing a property or attribute, After Effects assumes you mean the layer with the expression. When you reference an attribute of the property housing the expression, After Effects makes a similar assumption, allowing you to specify only the attribute name (without the entire comp/layer/property path). One side benefit of not having to specify the entire path is that you can apply the same expression to any property, without having to modify it at all.

The Rotation expression is very similar except that it doesn’t reference time. The reason for this is that the first expression is used to offset a keyframed animation in time, while the second expression simply creates a static (not animated) offset for the Rotation property. The first line of the expression defines a variable named offsetAngle. This variable defines the rotation amount (in degrees) by which each layer will be offset from the layer above it. The second line tells After Effects to calculate the layer’s offset and add it to the pre-expression value of the property.

You’ll see other ways to use index in later examples.

Looping Keyframes

The expression language provides two convenient ways to loop a sequence of keyframes: loopOut() and loopIn().

Suppose you keyframed a short animation and you want that sequence to repeat continuously. Simply add this expression to the keyframed property

loopOut("cycle")

and your animation will loop for the duration of the comp (Figure 10.5).

Figure 10.5. The solid line in the graph represents the keyframed bounce action. The dotted line represents the subsequent bounces created by loopOut("cycle").

image

Tip

image

A small glitch in the cycle version of loopOut() drops the first keyframe from each of the loops. If you want the frame with the first keyframe to be included, add a duplicate of the first keyframe one frame beyond the last keyframe.

There are three other variations of loopOut(), as well:

loopOut("pingpong") Runs your animation alternately forward, then backward.

loopOut("continue") Extrapolates the animation beyond the last keyframe, so the value of the property keeps moving at the same rate (and in the same direction, if you’re animating a spatial property such as Position) as the last keyframe. This can be useful, for example, if you’re tracking an object that has moved offscreen and you want After Effects to extrapolate where it would be if it kept moving at the same speed and in the same direction.

loopOut("offset") Works similarly to "cycle" except that instead of returning to the value of the first keyframe, each loop of the animation is offset by an amount equal to the value at the end of the previous loop. This produces a cumulative or stair-step effect.

loopIn() operates the same way as loopOut(), except that the looping occurs before the first keyframe instead of after the last keyframe. Both loopIn() and loopOut() will accept a second, optional parameter that specifies how many keyframes to loop. Actually, it’s easier to think of it as how many keyframed segments to loop. For loopOut() the segments are counted from the last keyframe toward the layer’s In point. For loopIn() the segments are counted from the first keyframe toward the layer’s Out point. If you leave this parameter out (or specify it as 0), all keyframes are looped. For example, this variation loops the segment bounded by the last and next-to-last keyframes:

loopOut("cycle",1)

Two variations on the expressions—loopOutDuration() and loopInDuration()—enable you to specify the time (in seconds) as the second parameter instead of the number of keyframed segments to be looped. For loopOutDuration(), the time is measured from the last keyframe toward the layer’s In point. For loopInDuration(), the time is measured from the first keyframe toward the layer’s Out point. For example, this expression loops the two-second interval prior to the last keyframe:

loopOutDuration("cycle",2)

If you leave out the second parameter (or specify it as 0), the entire interval between the layer’s In point and the last keyframe will be looped for loopOutDuration(). For loopInDuration(), the interval from the first keyframe to the Out point will be looped.

Using Markers

The expression language gives you access to the attributes of layer (and composition) markers. This can be extremely useful for synchronizing or easily establishing timing relationships between animated events.

The marker attributes that appear most frequently in expressions are time and index. As you might guess, the time attribute represents the time (in seconds) where the marker is located on the timeline. The index attribute represents the marker’s order on the timeline, where 1 represents the left-most marker. You can also retrieve the marker nearest to a time that you specify by using nearestKey(). For example, to access the layer marker nearest to the current comp time use

marker.nearestKey(time)

This can be handy, but more often you’ll want to know the most recent previous marker. The code necessary to retrieve it looks like this:

n = 0;
if (marker.numKeys > 0){
   n = marker.nearestKey(time).index;
   if (marker.key(n).time > time){
     n--;
   }
}

Note that this piece of code by itself is not very useful. When you do use it, you’ll always combine it with additional code that makes it suitable for the particular property to which the expression will be applied. Because it’s so versatile and can show up in expressions for virtually any property, it’s worth looking at in detail.

The first line creates a variable, n, and sets its value to 0. If the value is still 0 when the routine finishes, it means that at the current time no marker was reached or that there are no markers on this layer.

The next line, a JavaScript if statement, checks if the layer has at least one marker. If there are no layer markers, After Effects skips to the end of the routine with the variable n still set to 0. You need to make this test because the next line attempts to access the nearest marker with the statement

n = marker.nearestKey(time).index;

If After Effects attempted to execute this statement and there were no layer markers, it would generate an error and the expression would be disabled. It’s best to defend against these kinds of errors so that you can apply the expression first and add the markers later if you want to.

Tip

image

For more explanation of if statements, check out the “Conditionals” and “Comparison Operators” sections of the JavaScript guide.

If there is at least one layer marker, the third line of the expression sets n to the index of the nearest marker. Now all you have to do is determine if the nearest marker occurs before or after the current comp time with the statement

if (marker.key(n).time > time){
    n--;
}

This tells After Effects to decrement n by 1 if the nearest marker occurs later than the current time.

The result of all this is that the variable n contains the index of the most recent previous marker or 0 if no marker has yet been reached.

So how can you use this little routine? Consider a simple example.

Tip

image

If you’re wondering about the JavaScript decrement operator (--), it’s described in the “Operators” section of the JavaScript guide.

Trigger Animation at Markers

Say you have a keyframed animation that you want to trigger at various times. All you need to do is drop a layer marker (just press * on the numeric keypad) wherever you want the action to be triggered. Then, apply this expression to the animated property:

n = 0;
if (marker.numKeys > 0){
  n = marker.nearestKey(time).index;
  if (marker.key(n).time > time){
    n--;

  }
}if (n == 0){
  valueAtTime(0);
}else{
  t = time - marker.key(n).time;
  valueAtTime(t)
}

As you can see, it’s the previous marker routine with six new lines at the end. These lines tell After Effects to use the property’s value from time 0 if there are no previous markers. Otherwise, variable t is defined to be the time since the most recent previous marker, and the value for that time is used.

The result of this is that the animation will run, beginning at frame 0, wherever there is a layer marker.

Play Only Frames with Markers

Suppose you want to achieve a stop-motion animation effect by displaying only specific frames of your footage, say playing only the frames when your actor reaches the apex of a jump so he appears to fly or hover.

First enable time remapping for the layer, then scrub through the Timeline and drop a layer marker at each frame that you want to include. Finally, apply this expression to the Time Remap property:

n = marker.numKeys;
if (n > 0){
  f = timeToFrames(time);
  idx = Math.min(f + 1, n);
  marker.key(idx).time
}else{
  value
}

In this expression, the variable n stores the total number of markers for the layer. The if statement next checks whether there is at least one marker. If not, the else clause executes, instructing After Effects to run the clip at normal speed. If there are markers, the expression first calculates the current frame using timeToFrames(), which converts whatever time you pass to it into the appropriate frame number. Here, it receives the current comp time and returns the current frame number, which is stored in variable f.

Next you need to convert the current frame number to a corresponding marker index for the frame you actually want to display. It turns out that all you need to do is add 1. That means when the current frame is 0, you actually want to show the frame that is at marker 1. When frame is 1, you want to show the frame at marker 2, and so on. The line

idx = Math.min(f + 1, n);

calculates the marker index and stores it in the variable idx. Using Math.min() ensures the expression never tries to access more markers than there are (which would generate an error and disable the expression). Instead, playback freezes on the last frame that has a marker.

Notes

image

See “The Math Object” in the JavaScript guide for more information on Math.min().

Finally, you use the idx variable to retrieve the time of the corresponding marker. This value becomes the result of the expression, which causes After Effects to display the frame corresponding to the marker (Figure 10.6).

Figure 10.6. The bottom line in the graph represents how the Time Remap property would behave without the expression. As you would expect, it is a linear, gradual increase. The upper, stair-stepped line is the result of the expression. Because the expression plays only frames with markers (represented in the graph by small triangles), time advances much more quickly.

image

Time Remapping Expressions

There are many ways to create interesting effects with time remapping expressions. You’ve already seen one (the last expression in the previous section). Here are a few more illustrative examples.

Jittery Slow Motion

Here’s an interesting slow-motion effect where frames 0, 1, 2, and 3 play, followed by frames 1, 2, 3, and 4, then 2, 3, 4, and 5, and so on. First, enable time remapping for the layer and then apply this expression to the Time Remap property:

cycle = 4;
f = timeToFrames();
framesToTime(Math.floor(f/cycle) + f%cycle);

The first line sets the value of the variable cycle to the number of frames After Effects will display in succession (4 in this case). The second line sets variable f to the frame number corresponding to the current comp time. Next comes a tricky bit of math using JavaScript’s Math.floor() method and its % modulo operator. The result is a repeating sequence (whose length is determined by the variable cycle) where the starting frame number increases by 1 for each cycle.

Notes

image

For more detail on Math.floor() and the % modulo operator, see “The Math Object” and “Operators” sections of the JavaScript guide.

Wiggle Time

This effect uses multiple copies of the same footage to achieve a somewhat creepy echo effect. This effect actually involves three short expressions: one for Time Remap, one for Opacity, and one for Audio Levels. First, you enable time remapping for the layer. Then apply the three expressions and duplicate the layer as many times as necessary to create the look you want (Figure 10.7).

Figure 10.7. The time-wiggling effect with multiple layers.

image

Note that this time-wiggling effect is interesting, even with a single layer. The Opacity and Audio Levels expressions are necessary only if you want to duplicate the layer.

The expression for the Time Remap property is

Math.abs(wiggle(1,1))

wiggle() is an extremely useful tool that can introduce a smooth or fairly frenetic randomness into any animation, depending on your preference. wiggle() accepts five parameters, but only frequency and amplitude are required. Check the After Effects documentation for an explanation of what the remaining three optional parameters do.

The first parameter, frequency, represents the frequency of the wiggle in seconds; wiggle(1,1) varies the playback speed at the rate of once per second. The second parameter is the amplitude of the wiggle, given in the units of the parameter to which wiggle() is applied, which in this case is also seconds. So, wiggle(1,1) lets the playback time deviate from the actual comp time by as much as one second in either direction.

You use Math.abs() to make sure that the wiggled time value never becomes less than 0, which would cause the layer to sit at frame 0.

The Opacity expression gives equal visibility to each layer. Here’s what it looks like:

(index/thisComp.numLayers)*100

Notes

image

For more detail on Math.abs(), see “The Math Object” section of the online JavaScript guide.

This is simply the ratio of the layer’s index divided by the total number of layers in the comp, times 100%. That means if you duplicate the layer four times (for a total of five layers), the top layer will have an Opacity of 20%, the second layer will have an Opacity of 40%, and so on, until the bottom (fifth) layer, which will have an Opacity of 100%. This allows each layer to contribute equally to the final result.

If the footage has audio, you have a couple of choices. You can turn the audio off for all but one of the layers, or you can use an expression for Audio Levels that normalizes them so that the combined total audio level is roughly the same as it would be for a single layer. I think the second option enhances the creepiness of the effect; here’s the Audio Levels expression for a stereo audio source (for a mono source you could just leave out the second line of the expression):

db = -10*Math.log(thisComp.numLayers)/Math.log(10);
[db,db]

Notes

image

For more information on Math.log() see the “Math Object” section of the JavaScript guide on the accompanying disc; for more on arrays see the “Arrays” section.

This is just a little decibel math that reduces the level of each layer based on how many total layers there are (using the comp attribute numLayers). You’ll also notice a couple of JavaScript elements you haven’t encountered before: Math.Log() and an array (the second line of the expression). In expressions, you specify and reference the value of a multidimensional property, such as both channels of the stereo audio level, using array square bracket syntax.

Random Time

In this example, instead of having the time of each layer wander around, the expression offsets each layer’s playback time by a random amount. The expression you need for the Time Remap property is

maxOffset = 0.7;
seedRandom(index, true);
time + random(maxOffset);

The first thing to notice about this expression is the use of seedRandom() and random() and the relationship between these functions. If you use random() by itself, you get a different random number at each frame, which is usually not what you want. The solution is seedRandom(), which takes two parameters. The first is the seed. It controls which random numbers get generated by random(). If you specify only this parameter, you will have different random numbers on each frame, but they are an entirely new sequence of numbers. It’s the second parameter of seedRandom() that enables you to slow things down. Specifying this parameter as true tells After Effects to generate the same random numbers on each frame. The default value is false, so if you don’t specify this parameter at all, you get different numbers on each frame. It’s important to note that seedRandom() doesn’t generate anything by itself. It just defines the subsequent behavior of random().

Here’s an example. This Position expression randomly moves a layer to a new location in the comp on each frame:

random([thisComp.width,thisComp.height])

Close-up: More About random()

image

There are several ways to use random(). If you call it with no parameters, it will generate a random number between 0 and 1. If you provide a single parameter (as in the Random Time example), it will generate a random number between 0 and the value of the parameter. If you provide two parameters, separated by a comma, it will generate a random number between those two parameters. It’s important to note that the parameters can be arrays instead of numbers. For example, this expression will give you a random 2D position somewhere within the comp:

random ([thisComp.width,
thisComp.height])

In addition to random(), After Effects provides gaussRandom(), which operates in much the same way as random() except that the results have more of a Gaussian distribution to them. That is, more values are clustered toward the center of the range, with fewer at the extremities. Another difference is that with gaussRandom(), sometimes the values may actually be slightly outside the specified range, which never happens with random().

This variation causes the layer to stay in one random location:

seedRandom(1,true);
random([thisComp.width,thisComp.height])

This version is the same as the previous one, except that it generates a different, single random location because the value of the seed is different:

seedRandom(2,true);
random([thisComp.width,thisComp.height])

Let’s get back to the Time Remap expression. The first line creates the variable maxOffset and sets it to the maximum value, in seconds, that each layer’s playback time can deviate from the actual comp time. The maximum for the example is 0.7 seconds.

The next line tells After Effects that you want the random number generator (random()) to generate the same random number on each frame.

The last line of the expression calculates the final Time Remap value, which is just the sum of the current comp time plus a random offset between 0 and 0.7 seconds.

Next, you would apply the Opacity and Audio Levels expressions from the wiggle() example so that each layer’s video and audio will be weighted equally. Duplicate the layer as many times as necessary to get the effect you like.

Layer Space Transforms

In the world of expressions, layer space transforms are indispensible, but they present some of the most difficult concepts to grasp. There are three coordinate systems in After Effects, and layer space transforms provide you with the tools you need to translate locations from one coordinate system to another.

One coordinate system represents a layer’s own space. This is the coordinate system relative (usually) to the layer’s upper-left corner. In this coordinate system, [0, 0] represents a layer’s upper-left corner, [width, height] represents the lower-right corner, and [width, height]/2 represents the center of the layer. Note that unless you move a layer’s anchor point, it, too, will usually represent the center of the layer in the layer’s coordinate system.

The second coordinate system represents world space. World coordinates are relative to [0, 0, 0] of the composition. This starts out at the upper-left corner of a newly created composition, but it can end up anywhere relative to the comp view if the comp has a camera and the camera has been moved, rotated, or zoomed.

The last coordinate system represents comp space. In this coordinate system, [0, 0] represents the upper-left corner of the camera view (or the default comp view if there is no camera), no matter where the camera is located or how it is oriented. In this coordinate system, the lower-right corner of the camera view is given by [thisComp.width, thisComp.height]. In comp space, the Z coordinate really doesn’t have much meaning because you’re only concerned with the flat representation of the camera view (Figure 10.8).

Figure 10.8. This illustration shows the three coordinate systems of After Effects. Positions in the yellow layer’s coordinate system are measured relative to its upper-left corner. The 3D null is positioned at [0,0,0] in the comp so that it shows the reference point of the world coordinate system (here it’s exactly the same as the null’s layer coordinate system). The comp’s coordinate system is always referenced to the upper-left corner of the Comp view, which in this case no longer matches the world coordinate system because the camera has been moved and rotated.

image

So when would you use layer space transforms? One of the most common uses is probably to provide the world coordinates of a layer that is the child of another layer. When you make a layer the child of another layer, the child layer’s Position value changes from the world space coordinate system to layer space of the parent layer. That is, the child layer’s Position becomes the distance of its anchor point from the parent layer’s upper-left corner. So a child layer’s Position is no longer a reliable indicator of where the layer is in world space. For example, if you want another layer to track a layer that happens to be a child, you need to translate the child layer’s position to world coordinates. Another common application of layer space transforms allows you to apply an effect to a 2D layer at a point that corresponds to where a 3D layer appears in the comp view. Both of these applications will be demonstrated in the following examples.

Effect Tracks Parented Layer

To start, consider a relatively simple example: You have a layer named “star” that’s the child of another layer, and you want to rotate the parent, causing the child to orbit the parent. You have applied CC Particle Systems II to a comp-sized layer and you want the Producer Position of the particle system to track the comp position of the child layer. The expression you need to do all this is

L = thisComp.layer("star");
L.toComp(L.transform.anchorPoint)

The first line is a little trick I like to use to make the following lines shorter and easier to manage. It creates a variable L and sets it equal to the layer whose position needs to be translated. It’s important to note that you can use variables to represent more than just numbers. In this case the variable is representing a layer object. So now, when you want to reference a property or attribute of the target layer, instead of having to prefix it with thisComp.layer("star"), you can just use L.

In the second line the toComp() layer space transform translates the target layer’s anchor point from the layer’s own space to comp space. The transform uses the anchor point because it represents the layer’s position in its own layer space. Another way to think of this second line is “From the target layer’s own layer space, convert the target layer’s anchor point into comp space coordinates.”

This simple expression can be used in many ways. For example, if you want to simulate the look of 3D rays emanating from a 3D shape layer, you can create a 3D null and make it the child of the shape layer. You then position the null some distance behind the shape layer. Then apply the CC Light Burst 2.5 effect to a comp-sized 2D layer and apply this expression to the effect’s Center parameter:

L = thisComp.layer("source point");
L.toComp(L.anchorPoint)

(Notice that this is the same expression as in the previous example, except for the name of the target layer: source point, in this case). If you rotate the shape layer, or move a camera around, the rays seem to be coming from the position of the null.

Apply 2D Layer as Decal onto 3D Layer

Sometimes you may need to use more than one layer space transform in a single expression. For example, you might want to apply a 2D layer like a decal to a 3D layer using the Corner Pin effect. To pull this off you need a way to mark on the 3D layer where you want the corners of the 2D layer to be pinned. Apply four point controls to the 3D layer, and you can then position each of the 2D layer’s corners individually on the surface of the 3D layer. To keep things simple, rename each of the point controls to indicate the corner it represents, making the upper-left one UL, the upper-right UR, and so on. Once the point controls are in place, you can apply an expression like this one for the upper-left parameter to each parameter of the 2D layer’s Corner Pin effect:

L = thisComp.layer("target");
fromComp(L.toComp(L.effect("UL")("Point")))

The first line is just the little shorthand trick so that you can reference the target layer (the 3D layer in this case) more succinctly. The second line translates the position of point controls from the 3D layer’s space to the layer space of the 2D layer with the Corner Pin effect. There are no layer-to-layer space transforms, however, so the best you can do is transform twice: first from the 3D layer to comp space and then from comp space to the 2D layer. (Remember to edit the expression slightly for each of the other corner parameters so that it references the corresponding point control on the 3D layer.)

So, inside the parentheses you convert the point control from the 3D layer’s space into comp space. Then you convert that result to the 2D layer’s space. Nothing to it, right?

Reduce Saturation Away from Camera

Let’s change gears a little. You want to create an expression that reduces a layer’s saturation as it moves away from the camera in a 3D scene. In addition, you want this expression to work even if the target layer and the camera happen to be children of other layers. You can accomplish this by applying the Color Balance (HLS) effect to the target layer and applying this expression to the Saturation parameter:

minDist = 900;
maxDist = 2000;

C = thisComp.activeCamera.toWorld([0,0,0]);
dist = length(toWorld(transform.anchorPoint), C);
ease(dist, minDist, maxDist, 0, -100)

The first two lines define variables that will be used to set the boundaries of this effect. If the target layer’s distance from the camera is less than minDist, you’ll leave the Saturation setting unchanged at 0. If the distance is greater than maxDist you want to completely desaturate the layer with a setting of –100.

The third line of the expression creates variable C, which represents the position of the comp’s currently active camera in world space. It’s important to note that cameras and lights don’t have anchor points, so you have to convert a specific location in the camera’s layer space. It turns out that, in its own layer space, a camera’s location is represented by the array [0,0,0] (that is, the X, Y, and Z coordinates are all 0).

The next line creates another variable, dist, which represents the distance between the camera and the anchor point of the target layer. You do this with the help of length(), which takes two parameters and calculates the distance between them. The first parameter is the world location of the target layer and the second parameter is the world location of the camera, calculated previously.

All that’s left to do is calculate the actual Saturation value based on the layer’s current distance from the camera. You do this with the help of ease(), one of the expression language’s amazingly useful interpolation methods. What this line basically says is “as the value of dist varies from minDist to maxDist, vary the output of ease() from 0 to –100.”

Interpolation Methods

After Effects provides some very handy global interpolation methods for converting one set of values to another. Say you wanted an Opacity expression that would fade in over half a second, starting at the layer’s In point. This is very easily accomplished using the linear() interpolation method:

linear(time, inPoint, inPoint + 0.5, 0, 100)

As you can see, linear() accepts five parameters (there is also a seldom-used version that accepts only three parameters), which are, in order:

• input value that is driving the change

• minimum input value

• maximum input value

• output value corresponding to the minimum input value

• output value corresponding to the maximum input value

In the example, time is the input value (first parameter), and as it varies from the layer’s In point (second parameter) to 0.5 seconds beyond the In point (third parameter), the output of linear() varies from 0 (fourth parameter) to 100 (fifth parameter). For values of the input parameter that are less than the minimum input value, the output of linear() will be clamped at the value of the fourth parameter. Similarly, if the value of the input parameter is greater than the maximum input value, the output of linear() will be clamped to the value of the fifth parameter. Back to the example, at times before the layer’s In point the Opacity value will be held at 0. From the layer’s In point until 0.5 seconds beyond the In point, the Opacity value ramps smoothly from 0 to 100. For times beyond the In point + 0.5 seconds, the Opacity value will be held at 100.

Sometimes it helps to read it from left to right like this: “As the value of time varies from the In point to 0.5 seconds past the In point, vary the output from 0 to 100.”

Close-up: Expression Controls

image

Expression controls are actually layer effects whose main purpose is to allow you to attach user interface controls to an expression. These controls come in six versions:

• Slider Control

• Point Control

• Angle Control

• Checkbox Control

• Color Control

• Layer Control

All types of controls (except Layer Control) can be keyframed and can themselves accept expressions. The most common use, however, is to enable you to set or change a value used in an expression calculation without having to edit the code. For example, you might want to be able to easily adjust the frequency and amplitude parameters of a wiggle() expression. You could accomplish this by applying two slider controls to the layer with the expression (Effects > Expression Controls). It’s usually a good idea to give your controls descriptive names; say you change the name of the first slider to frequency and the second one to amplitude. You would then set up your expression like this (using the pick whip to create the references the sliders would be smart):

freq = effect("frequency")("Slider");
amp = effect("amplitude")("Slider");
wiggle(freq, amp)

Now, you can control the frequency and amplitude of the wiggle via the sliders. With each of the control types (again, with the exception of Layer Control) you can edit the numeric value directly, or you set the value using the control’s gadget.

One unfortunate side note about expression controls is that because you can’t apply effects to cameras or lights, neither can you apply expression controls to them.

The second parameter should always be less than the third parameter. Failure to set it up this way can result in some bizarre behavior.

Note that the output values need not be numbers. Arrays work as well. If you want to slowly move a layer from the composition’s upper-left corner to the lower-right corner over the time between the layer’s In point and Out point, you could set it up like this:

linear(time, inPoint, outPoint, [0,0], [thisComp.width, thisComp.height])

There are other equally useful interpolation methods in addition to linear(), each taking exactly the same set of parameters. easeIn() provides ease at the minimum value side of the interpolation, easeOut() provides it at the maximum value side, and ease() provides it at both. So if you wanted the previous example to ease in and out of the motion, you could do it like this:

ease(time, inPoint, outPoint, [0,0], [thisComp.width,
thisComp.height])

Fade While Moving Away from Camera

Just as you can reduce a layer’s saturation as it moves away from the camera, you can reduce Opacity. The expression is, in fact, quite similar:

minDist = 900;
maxDist = 2000;

C = thisComp.activeCamera.toWorld([0,0,0]);
dist = length(toWorld(transform.anchorPoint), C);
ease(dist, minDist, maxDist, 100, 0)

The only differences between this expression and the previous one are the fourth and fifth parameters of the ease() statement. In this case, as the distance increases from 900 to 2000, the opacity fades from 100% to 0%.

From Comp Space to Layer Surface

There’s a somewhat obscure layer space transform that you haven’t looked at yet, namely fromCompToSurface(). This translates a location from the current comp view to the location on a 3D layer’s surface that lines up with that point (from the camera’s perspective). When would that be useful?

Close-up: More About sampleImage()

image

You can sample the color and alpha data of a rectangular area of a layer using the layer method sampleImage(). You supply up to four parameters to sampleImage() and it returns color and alpha data as a four-element array (red, green, blue, alpha), where the values have been normalized so that they fall between 0.0 and 1.0. The four parameters are

• sample point

• sample radius

• post-effect flag

• sample time

The sample point is given in layer space coordinates, where [0, 0] represents the center of the layer’s top left pixel. The sample radius is a two-element array (x radius, y radius) that specifies the horizontal and vertical distance from the sample point to the edges of the rectangular area being sampled. To sample a single pixel, you would set this value to [0.5, 0.5], half a pixel in each direction from the center of the pixel at the sample point. The post-effect flag is optional (its default value is true if you omit it) and specifies whether you want the sample to be taken after masks and effects are applied to the layer (true) or before (false). The sample time parameter specifies the time at which the sample is to be taken. This parameter is also optional (the default value is the current composition time), but if you include it, you must also include the post-effect flag parameter. As an example, here’s how you could sample the red value of the pixel at a layer’s center, after any effects and masks have been applied, at a time one second prior to the current composition time:

mySample = sampleImage([width/
height]/2, [0.5,0.5], true, time
– 1);
myRedSample = mySample[0];

Imagine you have a 2D comp-sized layer named Beam, to which you have applied the Beam Effect. You want a Lens Flare effect on a 3D layer to line up with the ending point of the Beam effect on the 2D layer. You can do it by applying this expression to the Flare Center parameter of the Lens Flare effect on the 3D layer:

beamPos = thisComp.layer("beam").effect("Beam")
("Ending Point");
fromCompToSurface(beamPos)

First, store the location of the ending point of the Beam effect into the variable beamPos. Now you can take a couple of shortcuts because of the way things are set up. First, the Ending Point parameter is already represented as a location in the Beam layer’s space. Second, because the Beam layer is a comp-sized layer that hasn’t been moved or scaled, its layer space will correspond exactly to the Camera view (which is the same as comp space). Therefore, you can assume that the ending point is already represented in comp space. If the Beam layer were a different size than the comp, located somewhere other than the comp’s center, or scaled, you couldn’t get away with this. You would have to convert the ending point from Beam’s layer space to comp space.

Now all you have to do is translate the beamPos variable from comp space to the corresponding point of the surface of the layer with Lens Flare, which is accomplished easily with fromCompToSurface().

You’ll look at one more example of layer space transforms in the big finale “Extra Credit” section at the end of the chapter.

Color Sampling and Conversion

Here’s an example that demonstrates how you work with colors in an expression. The idea here is that you want to vary the opacity of an animated small layer based on the lightness (or luminosity) of the pixels of a background layer that currently happen to be under the moving layer. The smaller layer will become more transparent as it passes over dark areas of the background and more opaque as it passes over lighter areas. Fortunately, the expression language supplies a couple of useful tools to help out.

Before examining the expression, we need to talk about the way color data is represented in expressions. An individual color channel (red, blue, green, hue, saturation, lightness, or alpha) is represented as a number between 0.0 (fully off) and 1.0 (fully on). A complete color space representation consists of an array of four such channels. Most of the time you’ll be working in red, blue, green, and alpha (RGBA) color space, but you can convert to and from hue, saturation, lightness, and alpha (HSLA) color space. This example uses sampleImage() to extract RGBA data from a target layer called background. Then rgbToHsl() converts the RGBA data to HSLA color space so that you can extract the lightness channel, which will then be used to drive the Opacity parameter of the small animated layer. Here’s the expression:

sampleSize = [width, height]/2;
target = thisComp.layer("background");
rgba = target.sampleImage(transform.position,
sampleSize, true, time);
hsla = rgbToHsl(rgba);
hsla[2]*100

First you create the variable sampleSize and set its value as an array consisting of half the width and height of the layer whose opacity will be controlled with the expression. Essentially this means that you’ll be sampling all of the pixels of the background layer that are under smaller layers at any given time.

The second line just creates the variable target, which will be a shorthand way to refer to the background layer. Then sampleImage() retrieves the RGBA data for the area of the background under the smaller layer and stores the resulting array in the variable rgba. See the sidebar “More About sampleImage()” earlier in the chapter for details on all the parameters of sampleImage().

Next rgbToHsl() converts the RGBA data to HSLA color space and stores the result in variable hsla. Finally, because the lightness channel is the third value in the HSLA array, you use the array index of [2] to extract it (see the “Arrays” section of the JavaScript guide if this doesn’t make sense to you). Because it will be a value between 0.0 and 1.0, you just need to multiply it by 100 to get it into a range suitable to control the Opacity parameter (Figure 10.9).

Figure 10.9. The small blue layer becomes more transparent as it passes over darker areas of the background image.

image

Extra Credit

Congratulations on making it this far. The remaining examples build on concepts covered earlier, but I have saved them for this section because they are particularly tricky or involve some complex math. I’m presenting them mainly to entice you to take some time to figure out how they work.

Fade as Turn Away from Camera

Let’s briefly return to the world of layer space transforms and examine a simple idea that requires only a short expression, but one with a lot of complicated vector math going on under the hood. The idea is that you want a 3D layer to fade out as it turns away from the camera. This needs to work not only when the layer rotates away from the camera, but also if the camera orbits the layer. And of course, it should still work if either the layer or the camera happens to be the child of another layer. Take a look at an expression for Opacity that will accomplish this:

minAngle = 20;
maxAngle = 70;

C = thisComp.activeCamera.toWorld([0,0,0]);
v1 = normalize(toWorld(transform.anchorPoint) – C);
v2 = toWorldVec([0,0,1]);
angle = radiansToDegrees(Math.acos(dot(v1, v2)));
ease(angle, minAngle, maxAngle, 100, 0)

The first two lines just create two variables (minAngle and maxAngle) that establish the range of the effect. Here you set their values so that when the layer is within 20 degrees of facing the camera, it will be at 100% Opacity and Opacity will fade from 100% to 0% as the angle increases to 70 degrees. Beyond 70 degrees, Opacity will be 0%.

Next you create a variable C that represents the position of the comp’s active camera in world space. You’ve seen this before, in the expression where the layer fades as it moves away from the camera.

Now starts the vector math. Things get a little bumpy from here. Briefly, a vector is an entity that has a length and a direction, but has no definite position in space. I like to think of vectors as arrows that you can move around, but they always keep the same heading. Fortunately the expression language provides a pretty good arsenal of tools to deal with vectors.

To figure out the angle between the camera and the layer with the expression, you’re going to need two vectors. One will be the vector that points from the center of the layer toward the camera. The other will be a vector that points outward from the center of the layer along the z-axis.

To calculate the first vector (variable v1), convert the layer’s anchor point to world space coordinates and subtract from that value the location of the camera in world space. What you’re doing is subtracting two points in space. Remember, in After Effects, each 3D position in space is represented by an array: [x,y,z]. The result of subtracting two points like this gives you a vector. This vector has a magnitude representing the distance between the two points and a direction (in this case, the direction from the layer to the camera). You can use normalize() to convert the vector to what is known as a unit vector, which maintains the direction of the original vector but sets its length to 1. This simplifies the upcoming determination of the angle between two vectors.

Next you create the second vector (variable v2). You can create the necessary unit vector in one step this time by using toWorldVec([0,0,1]) to create a vector of length 1 pointed along the layer’s z-axis.

Now you have your two vectors. To calculate the angle between two vectors, you use what is known as the vector dot product. I won’t go into great detail about how it works (there’s a lot of information on the Internet if you’re curious), but it turns out that if you use unit vectors, the vector dot product will directly give you the arc cosine of the angle between the two vectors. Luckily, the expression language gives us a built-in function, dot(), to calculate the dot product.

So now you can calculate the angle you need (and store it in variable angle) in three steps. First you take the dot product of the two vectors, producing the arc cosine of the angle. Then you use Math.acos() to convert that to an angle (see the “Math Object” section of the JavaScript guide for more information). Because the result of Math.acos() will be in radians, you need to convert it to degrees so that it will be in the same units as the limits minAngle and maxAngle. Fortunately, the expression language provides radiansToDegrees() to make the conversion.

The final step is to use the interpolation method ease() to smoothly execute the fade as the angle increases.

Audio Triggers Effect

Earlier, you learned about linking an effect to an audio level. You can take that idea one step further and use audio to trigger an animated effect. The difference is subtle but significant. In the earlier examples, the effect tracked the audio level precisely, leaving the result at the mercy of the shape of the audio level’s envelope. Here, you’re going to use the transitioning of the audio level above some threshold to trigger an animation. The animation will run until there is another trigger event, which will cause the animation to start again from the beginning.

This is a powerful concept and there are many ways to use it. This example triggers a decaying oscillation that is actually contained within the expression, but you could easily adapt this to run a keyframed animation using valueAtTime() or to run a time-remapped sequence.

The heart of this expression is what I would call a “beat detector.” The expression basically walks backward in time, frame by frame, looking for the most recent event where the audio level transitioned from below the threshold to above the threshold. It then uses the difference in time between the triggering event and the current comp time to determine how far along it should be in the animation. At each new beat, this time resets to 0 and runs until the next beat. Take a look at this monster:

threshold = 20.0;

A = thisComp.layer("Audio Amplitude").effect("Both
Channels")("Slider");

// beat detector starts here

above = false;
frame = timeToFrames();
while (true){
  t = framesToTime(frame);
  if (above){
    if (A.valueAtTime(t) < threshold){
      frame++;
      break;
    }
  }else if (A.valueAtTime(t) >= threshold){
    above = true;
  }
  if (frame == 0){
    break;
  }
  frame--
}
if (! above){
  t = 0;
}else{
   t = time - framesToTime(frame);
}

// animation starts here

amp = 75;
freq = 5;
decay = 2.0;

angle = freq * 2 * Math.PI * t;
amp * (-Math.cos(angle)+1)/ Math.exp(decay * t);

This expression has three sections. The first section defines the audio level that you want to trigger the animation and stores it into the variable threshold. It then defines variable A to use as shorthand notation for the slider control containing the keyframed data for the audio level.

The next section is the actual beat detector. In general, the expression starts at the current comp time and determines if the level is currently above the threshold. If it is, the expression moves backward in time, frame by frame, until it finds the most recent frame where the audio level was below the threshold. It then determines that the triggering event occurred on the frame after that (the most recent frame where the level transitioned from below the threshold to above it). That transition frame is converted to time using framesToTime(), that value is subtracted from the current comp time, and the result (the time, in seconds, since the triggering event) is stored in variable t.

However, if instead the audio level at the current comp time is below the threshold, the expression has more work to do. It first moves backward from the current comp time, frame by frame, until it finds a frame where the audio level is above the threshold. Then it continues on, looking for the transition from below the threshold to above it. The elapsed time since the triggering event is then calculated and stored in variable t.

There are some other things going on in this routine, but they mostly have to do with special cases, such as a time when there hasn’t yet been a triggering event (in which case the animation is held at the first frame), or when the level is above the threshold but it has been there since the first frame.

Notes

image

See the “Comments” section of the JavaScript guide for more details on comments and the “Loops” section for more information about while(), break, and loops in general.

There are some JavaScript elements in this section that you haven’t seen before. Two forward slashes, //, denotes the start of a comment. The routine consists mainly of a giant while() loop. This loop is unusual in that its terminating condition is set to true, so it will never end on its own. It will continue to loop until one of the break statements is executed.

When After Effects arrives at the last section of the expression, variable t contains the necessary information: how long it has been since the last triggering event. The final section uses it to drive a decaying oscillation routine with Math.cos() and Math.exp(). First you define the amplitude of the oscillation with the variable amp. Then you define the frequency of the oscillation (oscillations per second) with the variable freq. Variable decay determines how fast the oscillation decays (a higher number means a faster decay).

Math.cos() creates an oscillating sine wave with amplitude amp and frequency freq, then Math.exp() reduces the amplitude of the oscillating wave at a rate determined by variable decay (Figure 10.10).

Figure 10.10. The graph shows the decaying oscillation triggered whenever the audio threshold level is crossed.

image

Notes

image

You might want to visit “The Math Object” section of the JavaScript guide for more information on Math.cos() and Math.exp().

Conclusion

This chapter covered a lot of ground, but still it really only provided a hint of what’s possible with expressions. Here are a few resources where you can find additional information:

www.aenhancers.com: A forum-based site where you can get your questions answered and take a look at expressions contributed by others

http://forums.creativecow.net/forum/adobe_after_effects_expressions: A forum dedicated to expressions

http://forums.adobe.com/community/aftereffects_general_discussion/aftereffects_expressions: Adobe’s own After Effects forum, which has a subforum on expressions

www.adobe.com/support/aftereffects: The online version of After Effects Help

www.motionscript.com: The site of the author of this chapter, which has a lot of examples and analysis

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset