part:proxy:clamp

clamp(x,min,max)

Used to limit the minimum/maximum value of an expression. Useful for things that need to stop at a given value.

clamp(input_value, lowest_value, highest_value)

input_value

The value that you want to limit, can be a function. A universal input that will work in most cases is start_value +/- timeex() * speed

lowest_value

This is the lowest value that the function can output. This value is used instead of input_value when input_value is lower than lowest_value.

highest_value

This is the highest value that the function can output. This value is used instead of input_value when input_value is higher than highest_value.

clamp(5 + timeex(), 5, 10)


When shown, the proxy starts at 5 and increases over time thanks to timeex(), clamp(input, min, max) is used to limit the output so the value never goes above 10.

clamp(10 - timeex(), 5, 10)


Same as above except the proxy starts at 10 and decreases over time thanks to timeex(), clamp(input, min, max) is used to limit the output so the value never goes below 5.


Deep dive : Standard Clamp fades (normalized in [0,1] range)

fade-in clamp(1*timeex(), 0, 1)
fade-out clamp(1 - 1*timeex(), 0, 1)

I would find it useful to take some time examining why you'd want to use a normalized clamp more often. The main reason is to think in terms of time and to allow for flexibility through modularity.

While some people wish to think in terms of real units when expressing their speed, that is not always useful if you want to adapt your transition to another variable, because when you change the intended output range, you also need to change the speed based on the minimum and maximum.

That's why I prefer thinking of the time it takes to perform the movement first and foremost, that way I can focus on the time aspect first, then decide the amounts. I find it easier to work that way. Your theoretical approach is up to you, but while theory is just theory, it's also true that how you think impacts your work. But let's take a look at how that'd look like.

clamp(1*timeex(), 0, 1)
clamp(1 - 1*timeex(), 0, 1)

clamp(speed*timeex(), 0, 1)
clamp(1 - speed*timeex(), 0, 1)

The basic template lasts for 1 second. In terms of time, for quite a few use cases, that is already a decent speed and time to use by itself, but you'd want to know how to change that based on what you're looking for. How do you translate speed into time?

Based on the equation : speed * time = distance, the speed is found by comparing the distance and the desired time:

speed = distance / duration

In a normalized range, “distance” is just 1, without units. I'm just pointing this out in general. You don't have to change that value if you use a normalized clamp.

e.g. for 1 second
  speed = 1 / 1s = 1
e.g. for 2 seconds
  speed = 1 / 2s = 0.5
e.g. for 0.5 seconds
  speed = 1 / 0.5s = 2
generalization: 
  speed = 1 / duration

So you can work with numbers ranging from 0 to 1, but obviously you have bigger numbers in other variable names. So while you have control of the time aspect, how do you make the movement bigger?

100*clamp(1*timeex(), 0, 1)
Easy. Multiply outside the clamp. If you know *where* to multiply, it saves you a lot of trouble. Generally you do it outside.

100*clamp(1 - 1*timeex(), 0, 1)
The same applies for the reverse of your pair. It doesn't matter if you have bigger values. You still don't need to change the inside unless you want to change the duration of the movement. That's the usefulness of separating the time and the amount of movement.

The value of multiplication is evident once you understand its effect. Turns out, 0 makes anything 0, and 1 keeps anything the same. The job of standard clamp fades is to move between these two concepts of neutrality/nullity and fullness/unity. One way to think about the effect of multiplication is that it's composition.

What does this mean? While it may seem trivial to say it combines the different functions or elements together, it's interesting and useful when you also think of that as modifiers. It allows you to supercharge your concepts and combine ideas practically. To make full use of proxies, you must first have some basic elements and concepts, and then to know how to spot them, and build from them. Not necessarily in that order. You can do first, then eventually conceptualize and analyze what you see. I'd argue this is part of what separates serious proxy users from people who merely rely on others to give them expressions to copy and paste.

Let's say you have 100. A distance. Plain number. But what do you do with it? Go there? So that means gradually going up to 100. Sound familiar? That's the clamp! The clamp turns your simple 100 number into a dynamic moving system going up to 100. Sure it's a simple setup but the next step is to try it with other stuff.

Now what if, instead of 100 (or with 100), the multiplier was something else like a sine? You know sines, the wavy functions. The concept is waviness, bobbing or shaking.

sin(6*time())

Of course, you'll remember that multiplying by 0 makes anything 0. So what would happen to a sine under these conditions? No waviness. Nothing happens. Boring? Perhaps.
Next, you'll also remember that 1 leaves things unchanged, or back to normal or full amount. Nothing changed. Why bother? Indeed, why multiply by 1 if it doesn't change the rest?
Now think of the fade. Combine these concepts as you observe what the fade-in does to the sine over time. It grows into a stronger pulse.
Your concept is now “an increasing pulse”. Now you think of related concepts of lights or other stuff bobbing up and down, and you can apply to them the concept of “starting up” or “fading out”, “stabilizing” or whatever your mind comes up with and understands. And you'll remember that multiplication is the glue, the gears, that brings these to work together as a unified, more interesting whole.

-50 + 150*clamp(1*timeex(), 0, 1)
If your start is -50 and you need to end up at 100, you work out the math by the equation of delta = end - start

0 initial equation
  delta = end - start
1 define variables
  1.1 : end = 100
  1.2 : start = -50
2 plug the values in
  delta = 100 - (-50)
3
  delta = 100 + 50 = 150

clamp(-2 + 1*timeex(), 0, 1)
Delays are done by starting beyond the bounds. You work out the math by the equation of y = ax + b

0 initial equation

y = ax + b

1 define variables:

1.1 : This equation is solved by this requirement : we define the delay as the x value for the crossing point
  y = 0 (for fade-ins)
  or
  y = 1 (for fade-outs)
1.2 : a is speed
  1.2.1 : earlier, I proved that speed = 1/duration
1.3 : x is time
  1.3.1 : Therefore, x is the delay, because that's what we want to resolve the equation
1.3 : b is a constant offset, that is what we're looking for

2 reformatting & renaming the variables

y = ax + b
  side note: this is just the standard expression: y = clamp(speed*timeex() + b, 0, 1)
b = y - ax
b = 0 - speed * delay

3 solution for the [0,1] clamp

FADEIN: y=0, speed is still positive in the actual expression, we're just calculating the delay offset
b = -speed * delay
b = -delay / duration
FADEOUT: y=1, remember speed is negative. I flipped the sign accordingly.
b = 1 + speed * delay
b = 1 + delay / duration

4 examples (plugging in the desired values)

delay = 3s, speed = 2, fade-in (y = 0)
b = 2 * 3
clamp(-6 + 2*timeex(),0,1)
delay = 0.5s, fade-out (y = 1, speed = -3)
b = 1 + 3 * 0.5
clamp(2.5 - 3*timeex(),0,1)

In practice, movement is often done in pairs. You go out, you go back. You send something up, you it goes back down. You become invisible, you become visible.

consider:
-50 + 150*clamp(-1 + timeex(),0,1)

There are three approaches you might take when changing from fade-in to fade-out, from forward to back.
First is adopting the fade-out template.

conversions:
fadein
  -50 + 150*clamp(-1 + timeex(),0,1)
fadeout
  -50 + 150*clamp(2 - timeex(),0,1)

Second is thinking in reverse. You can do a fadeout by simply starting from the maximum value and subtracting a fade-in. Think about it. You go somewhere, that means a movement forward. But when you return home, you don't walk backward. It just means you start at (what was) the end, and you still walk forward but in the reverse direction.

consider: your start is -50, your end is 100. from that, the distance traveled is 150.
  -50 + 150*clamp(-1 + timeex(),0,1)
the trip back just means the end now corresponds to the start and the travel is reversed
  100 - 150*clamp(-1 + timeex(),0,1)

Third is using lerp or eases to adjust for the start and end for you, letting it run based on the fade-in. Those functions support it so don't overcomplicate things. You wouldn't normally want to reverse your clamp if you're using eases, because it would reverse the polarity.

consider:
lerp(clamp(-1 + timeex(),0,1),-50,100)
lerp(clamp(-1 + timeex(),0,1),100,-50)
eases bring cool dynamics
easeInBack(clamp(-1 + timeex(),0,1),-50,100)
easeInBack(clamp(-1 + timeex(),0,1),100,-50)

Finally, what if you want to perform both movements? It'll depend on whether you want the schedule to be predetermined, or controlled. Let's examine both approaches.

twin clamps : fadein*fadeout

clamp(timeex(), 0, 1) * clamp(3 - timeex(), 0, 1)
This approach makes sense if you want a simplified setup that runs one proxy, which may not need events. This makes your tree less heavy. But it's only ever useful if your sequence is predefined on a set duration / schedule, where the time doesn't change. Which might be good for single, short swells and stuff.

Another use case is a basic babbler (approximated mouth movement) for a predefined voiceline, used in a flex or faceposer part. Notice the power sine, serving as a basic unit to make the mouth move “somewhat” naturally while still being reasonably mathematically simple.

clamp(3*timeex(), 0, 1) * clamp(3 - timeex(), 0, 1) * (sin(12*time())^2)

But let's go back to explaining why twin clamps work, hammered in more concrete terms than “trust me bro”. Essentially, to understand that, we reduce or simplify what the expression means at certain points in time for reference.

  1. at t = 0s : The fade-out term is delayed. That means that for a while, it's not doing anything. In other words, it's multiplying the fade-in by 1. Just like x + 0 = x, 1*x = x. Looks obvious and redundant when I write these out, but you just needed to notice that the fade-out and fade-in can be those “nothing terms” at some points during their life.
  2. until t = 2s : Because the fade-out, not doing anything, can be ignored for its 2 second delay, all that really exists is the fade-in. So that's what the proxy does. A fade-in.
  3. at t = 1s : But then the fade-in reaches its end. 1. Yet that is reducible. So, in a way, it no longer exists from now on. All that remains is 1 until the fade-out starts moving.
  4. at t = 2s : 1 second later after the fade-in stops, the fade-out starts to move. So we can see what it does. And since the fade-in already did its job and ended at 1… All that really happens in the expression is the fade-out. So that's what the proxy does. A fade-out.

Of course, you could've also done something like clamp(timeex(), 0, 1) - clamp(-2 + timeex(), 0, 1) since fade-outs are just reverse fade-ins…

proxy-event pairs

clamp(timeex(), 0, 1) with an inverted event
clamp(3 - timeex(), 0, 1) with an uninverted event
This approach is required if you want manual control of your movement using event pairs. For instance, when you don't know in advance what time you want to deploy or retract some mechanism. That would be decided by button events, toggled command events etc. under your control.
You would have two proxies and two events. We expect one event to be off when the other is on, otherwise the proxies will compete. Having more proxies and more events is some complexity by itself and it weighs down your outfit just a little bit (performance-wise, it adds up eventually), but control with events is a requirement in many setups.

Recap : Key points

Unnormalized clamps : Real units

clamp(5 + timeex(), 5, 10)
clamp(10 - timeex(), 5, 10)
For thinking in terms of real units first, directly done inside the clamp.

Normalized clamps : Simplifying the time

clamp(timeex(), 0, 1)
clamp(1 - timeex(), 0, 1)
clamp(speed*timeex(), 0, 1)
clamp(1 - speed*timeex(), 0, 1)
For thinking in terms of time, multiplying outside has benefits. We have seen some equations to find out the speed and delaying offsets.

speed = 1 / duration
fade-in:
  b = -delay / duration
  b = -delay * speed
  e.g. clamp(-2 + timeex(),0,1)
fade-out
  b = 1 + delay * speed
  e.g. clamp(3 - timeex(),0,1)
The equivalence between fade-in and fade-out : Reverse symmetry

The equivalence reveals itself if you do a reverse (subtract) of the fade-in starting at its end value (the maximum of the fade-in is the start of the fade-out) -50 + 150*clamp(timeex(), 0, 1)
100 - 150*clamp(timeex(), 0, 1)

The value of multiplication : Composition of concepts

Whether it's to apply your normalized fade to some real distance or to combine multiple concepts together, multiplication brings these ideas together. Multiplication being composition is a crucial principle to understand proxy math. And the normalized fade's purpose is to move back and forth between the concept of nullity/neutrality and unity/fullness, which become useful when applied to other phenomena.

Simplify movements using lerp or eases

With the fade-in as a controller term, these functions' second and third arguments do some of the work of adjusting distances for you.
lerp(clamp(timeex(), 0, 1),-50,100)
easeInBack(clamp(timeex(), 0, 1),100,-50)

  • Last modified: 9 days ago
  • by pingu7867