Icon Scalar Velocity

 

Icon Tags, Ranges and Masks

 

Icon Fitting Code Driven Displacement

 

Icon atoi and Trillions of Whales

 

Icon SuperTrack: Motion Tracking for Physically Simulated Characters using Supervised Learning

 

Icon Joint Limits

 

Icon Code vs Data Driven Displacement

 

Icon Exponential Map, Angle Axis, and Angular Velocity

 

Icon Encoding Events for Neural Networks

 

Icon Visualizing Rotation Spaces

 

Icon Spring-It-On: The Game Developer's Spring-Roll-Call

 

Icon Interviewing Advice from the Other Side of the Table

 

Icon Saguaro

 

Icon Learned Motion Matching

 

Icon Why Can't I Reproduce Their Results?

 

Icon Latinendian vs Arabendian

 

Icon Machine Learning, Kolmogorov Complexity, and Squishy Bunnies

 

Icon Subspace Neural Physics: Fast Data-Driven Interactive Simulation

 

Icon Software for Rent

 

Icon Naraleian Caterpillars

 

Icon The Scientific Method is a Virus

 

Icon Local Minima, Saddle Points, and Plateaus

 

Icon Robust Solving of Optical Motion Capture Data by Denoising

 

Icon Simple Concurrency in Python

 

Icon The Software Thief

 

Icon ASCII : A Love Letter

 

Icon My Neural Network isn't working! What should I do?

 

Icon Phase-Functioned Neural Networks for Character Control

 

Icon 17 Line Markov Chain

 

Icon 14 Character Random Number Generator

 

Icon Simple Two Joint IK

 

Icon Generating Icons with Pixel Sorting

 

Icon Neural Network Ambient Occlusion

 

Icon Three Short Stories about the East Coast Main Line

 

Icon The New Alphabet

 

Icon "The Color Munifni Exists"

 

Icon A Deep Learning Framework For Character Motion Synthesis and Editing

 

Icon The Halting Problem and The Moral Arbitrator

 

Icon The Witness

 

Icon Four Seasons Crisp Omelette

 

Icon At the Bottom of the Elevator

 

Icon Tracing Functions in Python

 

Icon Still Things and Moving Things

 

Icon water.cpp

 

Icon Making Poetry in Piet

 

Icon Learning Motion Manifolds with Convolutional Autoencoders

 

Icon Learning an Inverse Rig Mapping for Character Animation

 

Icon Infinity Doesn't Exist

 

Icon Polyconf

 

Icon Raleigh

 

Icon The Skagerrak

 

Icon Printing a Stack Trace with MinGW

 

Icon The Border Pines

 

Icon You could have invented Parser Combinators

 

Icon Ready for the Fight

 

Icon Earthbound

 

Icon Turing Drawings

 

Icon Lost Child Announcement

 

Icon Shelter

 

Icon Data Science, how hard can it be?

 

Icon Denki Furo

 

Icon In Defence of the Unitype

 

Icon Maya Velocity Node

 

Icon Sandy Denny

 

Icon What type of Machine is the C Preprocessor?

 

Icon Which AI is more human?

 

Icon Gone Home

 

Icon Thoughts on Japan

 

Icon Can Computers Think?

 

Icon Counting Sheep & Infinity

 

Icon How Nature Builds Computers

 

Icon Painkillers

 

Icon Correct Box Sphere Intersection

 

Icon Avoiding Shader Conditionals

 

Icon Writing Portable OpenGL

 

Icon The Only Cable Car in Ireland

 

Icon Is the C Preprocessor Turing Complete?

 

Icon The aesthetics of code

 

Icon Issues with SDL on iOS and Android

 

Icon How I learned to stop worrying and love statistics

 

Icon PyMark

 

Icon AutoC Tools

 

Icon Scripting xNormal with Python

 

Icon Six Myths About Ray Tracing

 

Icon The Web Giants Will Fall

 

Icon PyAutoC

 

Icon The Pirate Song

 

Icon Dear Esther

 

Icon Unsharp Anti Aliasing

 

Icon The First Boy

 

Icon Parallel programming isn't hard, optimisation is.

 

Icon Skyrim

 

Icon Recognizing a language is solving a problem

 

Icon Could an animal learn to program?

 

Icon RAGE

 

Icon Pure Depth SSAO

 

Icon Synchronized in Python

 

Icon 3d Printing

 

Icon Real Time Graphics is Virtual Reality

 

Icon Painting Style Renderer

 

Icon A very hard problem

 

Icon Indie Development vs Modding

 

Icon Corange

 

Icon 3ds Max PLY Exporter

 

Icon A Case for the Technical Artist

 

Icon Enums

 

Icon Scorpions have won evolution

 

Icon Dirt and Ashes

 

Icon Lazy Python

 

Icon Subdivision Modelling

 

Icon The Owl

 

Icon Mouse Traps

 

Icon Updated Art Reel

 

Icon Tech Reel

 

Icon Graphics Aren't the Enemy

 

Icon On Being A Games Artist

 

Icon The Bluebird

 

Icon Everything2

 

Icon Duck Engine

 

Icon Boarding Preview

 

Icon Sailing Preview

 

Icon Exodus Village Flyover

 

Icon Art Reel

 

Icon LOL I DREW THIS DRAGON

 

Icon One Cat Just Leads To Another

Scalar Velocity

Created on May 4, 2022, 9:34 p.m.

In a previous post I explained what the angular velocity is, how to compute it, and how it relates to the angle-axis representation of rotations, as well as the so called exponential map.

Today I want to talk about something different, but similar: scale - as in the scale of objects we might place in a 3D world. And with that I have a question for you: what does velocity mean when it comes to scale. What is scalar velocity?

More specifically - if we have an object with a scale that is changing over time, how can we represent that rate of change, how can we compute it, and how can we interpret it?

Well one simple thing we could try is to just take the difference between two scale values at different times, and then divide that difference by the dt:

float scale_differentiate_velocity_naive(float next, float curr, float dt)
{
    return (next - curr) / dt;
}

Then, if we wanted to scale an object from, say, a scale of 0.1 to a scale of 10, over a period of 3 seconds, the velocity would be given by (10 - 0.1) / 3, and we would add fixed increments of this velocity, multiplied by the dt, onto the scale value of the object at each frame.

float scalar_velocity = scale_differentiate_velocity_naive(10.0f, 0.1f, 3.0f);

...
// Each Frame
scale = scale + dt * scalar_velocity;

But if we do this the growth appears fast at the beginning, but slows down as the object gets larger:

The reason this doesn't really work is because, scales, just like rotations, naturally compose using multiplication, rather than addition.

In fact we can see this, if we instead multiply the scale by some fixed rate each frame.

// Each Frame
scale = 1.025 * scale;

Doing things this way we get the visually continuous growth we'd expect:

And this already gives us a bit more intuition for what a scalar velocity should be - not a number we add each frame, but more like a ratio - a value which we multiply our scale values by each frame.

But we're not quite there yet, because it still isn't clear how the dt is involved in all of this.

One way to get closer to the correct answer is to think about the equivalent situation for rotations. Just as with scale, if we want to rotate an object by a fixed amount each frame, we multiply it by some fixed rotation, which we can call the delta:

// Each Frame
rotation = quat_mul(delta, rotation);

But the delta itself isn't the angular velocity. If you recall from my previous article, the delta is what we get if we take the angular velocity, multiply it by the dt, and convert it back from the scaled-angle-axis representation into a quaternion:

quat delta = quat_from_scaled_angle_axis(angular_velocity * dt);

And because scales, just like rotations, compose with multiplication, to get our scalar velocity we need to follow this same pattern.

We need to start with the scalar velocity, multiply by the dt, and put it through the exp function (the equivalent to our from_scaled_angle_axis function in this case):

float delta = expf(scalar_velocity * dt);

Which can be written as an integration function as follows:

float scale_integrate_velocity_natural(float vel, float curr, float dt)
{
    return expf(vel * dt) * curr;
}

To compute the scalar velocity we therefore do the inverse - we divide one scale value by the other, put the result through the log function, and divide it by the dt.

float scale_differentiate_velocity_natural(float next, float curr, float dt)
{
    return logf(next / curr) / dt;
}

If we're a bit more explicit about inverting and multiplying scales, notice how closely this resembles the quaternion versions of these functions:

float scale_inv(float s)
{
    return 1.0f / s;
}

float scale_mul(float s, float t)
{
    return s * t;
}

vec3 quat_differentiate_angular_velocity(quat next, quat curr, float dt)
{
    return quat_to_scaled_angle_axis(quat_abs(
        quat_mul(next, quat_inv(curr)))) / dt; 
}

float scale_differentiate_velocity_natural(float next, float curr, float dt)
{
    return logf(
        scale_mul(next, scale_inv(curr))) / dt;
}

quat quat_integrate_angular_velocity(vec3 vel, quat curr, float dt)
{
    return quat_mul(quat_from_scaled_angle_axis(vel * dt), curr);
}

float scale_integrate_velocity_natural(float vel, float curr, float dt)
{
    return scale_mul(expf(vel * dt), curr);
}

So the scalar velocity is the log of the ratio of two scales, divided by the dt.


Log Base

In these examples we've been using the natural log, but we can actually use any base we want, and changing the base of the log will change how we interpret the scalar velocity.

For example, if we use the natural log as in the above examples, then \( \log \tfrac{1}{1} \) will correspond to a scalar velocity of \( 0 \), \( \log \tfrac{e}{1} \) will correspond to a scalar velocity of \(1\), and \( \log \tfrac{1}{e} \) will correspond to a scalar velocity of \( -1 \).

If we use \( \log_2 \) on the other hand, we get the following: \( \log_2 \tfrac{1}{1} = 0 \), \( \log_2 \tfrac{2}{2} = 1 \), \( \log_2 \tfrac{1}{2} = -1 \).

float scale_differentiate_velocity(float curr, float prev, float dt)
{
    return log2f(curr / prev) / dt;
}

float scale_integrate_velocity(float vel, float curr, float dt)
{
    return exp2f(vel * dt) * curr;
}

(Note: We can still use the natural \( \log \) and \( \exp \) functions to compute things in base 2, so long as we multiply or divide the result by \( log(2) = 0.6931471805599453 \).)

#define LN2f 0.6931471805599453f

float scale_differentiate_velocity_alt(float curr, float prev, float dt)
{
    return (logf(curr / prev) / LN2f) / dt;
}

float scale_integrate_velocity_alt(float vel, float curr, float dt)
{
    return expf(LN2f * vel * dt) * curr;
}

When we use a base of 2 our scalar velocity gains an interpretable meaning: it represents the number of times an object will double in size every second (or halve in size for negative values). So an object with a scalar velocity of 3 is an object which will be eight times larger after one second.

And weird as it may sound, the scalar velocity (in base 2) is exactly this: the rate of doubling per second.


The Doublelife

The extremely keen eye'd of you might have noticed something a bit like this before in one of my articles. Take a look at this slightly re-arrange version of the damper_implicit function from my springs article.

float damper_implicit(float x, float g, float halflife, float dt)
{
    return lerp(x, g, 1.0f - expf(-LN2f * (1.0f / halflife) * dt));
}

Here, when we made our implicit damper use a halflife, we ended up taking 1.0f / halflife, multiplying it by a dt, converting to base 2 by multiplying by LN2f, negating it, and putting it through the exp function. That's a remarkably similar process to our scale_integrate_velocity function!

float scale_integrate_velocity_alt(float vel, float curr, float dt)
{
    return expf(LN2f * vel * dt) * curr;
}

By comparing the two we can see that -1.0f / halflife is kind of like the scalar velocity in this case. This gives us another intuitive way to interpret our scalar velocities. One over a negative (base 2) scalar velocity is a halflife!

Which means that one over a positive (base 2) scalar velocity is a... doublelife?


Lerp and Eerp

When we interpolate two positions we can use lerp, and with two rotations we can use slerp, but what about for scales?

A function you might have seen is eerp, which is a version of lerp that uses multiplication, division, and power, instead of addition, subtraction, and multiplication:

float lerpf(float x, float y, float a)
{
    return x * (1.0f - a) + y * a;
}

float eerpf(float x, float y, float a)
{
    return powf(x, (1.0f - a)) * powf(y, a);
}

float lerpf_alt(float x, float y, float a)
{
    return x + (y - x) * a;
}

float eerpf_alt(float x, float y, float a)
{
    return x * powf(y / x, a);
}

If we think about our previous intuition for dealing with scales - namely that scales (just like rotations) compose using multiplication rather than addition - then using this function for scales totally makes sense.

And converting all + to *, - to /, and * to pow is one way to do it, but another interesting way to do it is to convert these scale values into what resembles scalar velocities - to put them through log, use lerp, and then put the result back through exp:

float eerpf_alt2(float x, float y, float a)
{
    return expf(lerpf(logf(x), logf(y), a));
}
The reason this works is that it's algebraically identical to the previous formulation. Which we can see if we remember a few of our logarithm identities from school and do a little bit of algebra:

\begin{align*} \text{eerp}(x, y, a) &= \exp(\text{lerp}(\log(x), \log(y), a)) \\ \text{eerp}(x, y, a) &= \exp(\log(x) + (\log(y) - \log(x)) \times a) \\ \text{eerp}(x, y, a) &= \exp(\log(x)) \times \exp((\log(y) - \log(x)) \times a) \\ \text{eerp}(x, y, a) &= x \times \exp((\log(y) - \log(x)) \times a) \\ \text{eerp}(x, y, a) &= x \times \exp((\log(y) - \log(x)))^a \\ \text{eerp}(x, y, a) &= x \times \left(\frac{\exp(\log(y))}{\exp(\log(x)}\right)^a \\ \text{eerp}(x, y, a) &= x \times \left(\frac{y}{x}\right)^a \\ \end{align*}

Personally I think this is a cool example of the fundamental (but still mind-boggling to me) fact about logarithms: that adding and subtracting with logarithms, is the same as multiplying and dividing normally!


Scale Springs

In my previous article on springs I provided a little bit of example code for how we might make a quaternion spring:

void simple_spring_damper_implicit_quat(
    quat& x, 
    vec3& v, 
    quat x_goal, 
    float halflife, 
    float dt)
{
    float y = halflife_to_damping(halflife) / 2.0f;	
	
    vec3 j0 = quat_to_scaled_angle_axis(quat_mul(x, quat_inv(x_goal)));
    vec3 j1 = v + j0*y;
	
    float eydt = fast_negexp(y*dt);

    x = quat_mul(quat_from_scaled_angle_axis(eydt*(j0 + j1*dt)), x_goal);
    v = eydt*(v - j1*y*dt);
}

In formulating this quaterion spring we faced the same basic problem that we have with scales - that we needed to convert our quaternions (which normally require multiplication) into something we can add, subtract, and scale as if they were normal vectors, to allow them to be used in the spring equations.

Scales are no different, and the formulation of a scale spring looks remarkably similar to the quaternion one:

void simple_spring_damper_implicit_scale(
    float& x, 
    float& v, 
    float x_goal, 
    float halflife, 
    float dt)
{
    float y = halflife_to_damping(halflife) / 2.0f;	
	
    float j0 = log2f(x / x_goal);
    float j1 = v + j0*y;
	
    float eydt = fast_negexp(y*dt);

    x = exp2f(eydt*(j0 + j1*dt)) * x_goal;
    v = eydt*(v - j1*y*dt);
}

But it isn't just lerp and springs which can make use of this transformation.

Using log2 and exp2 for scales can be useful in all kinds of different situations where we are trying to adapt equations which assume the object is some kind of vector which can be added and subtracted. For example, almost all of linear algebra and machine learning!


Conclusion

Just like angular velocities, scalar velocities are not immediately intuitive. But that doesn't mean they are magical numbers which can't be understood, and knowing a little bit about them can give us great intuitions for how to deal with them in a whole host of different situations. Happy scaling!

github twitter rss