Add Vector3.Slerp

I have had many instances where I had to copy and paste a lerp function from some website (or translate the one from Unity to Lua if that’s faster) because it was not built in. (and its kinda annoying to do so)
It’d be really nice to see a built in lerp method in Roblox.

That would be fun. What is RFC though?

An RFC (“Request For Comments”) in the case of Luau is when you create a sort of feature request for the programming language that you want the Luau team to look into and implement:

2 Likes

It’s not the implementation that I was objecting too, but rather using the name “slerp” for what amounts to an analytic continuation of geometric slerp to non-unit vectors, which results in paths on ellipsoids, rather than spheres. This is a special-purpose function that IMO doesn’t belong as part of the base Vector3 library. If you had other datatypes that derive from Vector3, like for example UnitVector3 or RotationVector3, then it would make sense to implement slerp for these derives classes. And in this case, UnitVector3:Slerp() would be a completely different function from RotationVector3:Slerp(), since the former would be the geometric slerp of direction vectors, and the latter would be the classic slerp between two fully-specified 3D orientations.

Well, I don’t really know what a RotationVector3 is, could you describe what that is and how that is different from a Vector3?

What I meant is if you were making a math library, and you had 3-element classes that derived from a 3-vector class, but are implementing specific encodings, then it would make sense for some of those specific classes to have a slerp. By rotation vector, I mean the an axis-angle vector:

What would the API look like for slerping with RotationVector3s?

It would just look like a Slerp function that takes two rotation vectors and returns a rotation vector. I’m not advocating for this to be implemented; rotation vectors are nasty for two reasons: They lose precision for small angles (since length of the vector is what encodes the angle), but even worse is the fact that while they appear to be vectors, rotations (SO(3)) are not a vector space, so all of the normal Vector3 operations like addition, dot product, etc. are basically meaningless for them. To implement Slerp for them, the easiest thing would be to convert to quaternions, slerp, and convert back, except in the special case where they share the same axis, in which case you can just lerp the length. But unlike the Unity vector3 lerp, you can’t lerp the direction of these vectors, since they are not directions, they are rotation axes.

The simplest solution to the OP’s request is to just add a helper function to Vector3 to interpolate direction and length. I can do what Unity’s Vector3:Slerp does, it just makes it clear that it’s more than just a Slerp and is valid for any length vectors.

1 Like

Sorry for the late reply, our team has been in the process of moving to Reno.

So I guess my problem with this, now that I know what you mean, is that this conception of RotationVectors does not really play well with existing math.

TL;DR


  • Rotation vectors are a linear space, it makes sense to add and scalar multiply them.
  • It is not very useful to slerp rotation vectors because they are already in log/angular space; they come about naturally as the logarithm of a quaternion, matrix, or geometric algebraic object.
  • There is a very natural and pretty much only one correct way to define slerp, and unitization is not a part of that.
    The definition, a * (a^-1 * b)^t, naturally works for non-unit inputs, and guarantees a constant log-angular derivative of log(a^-1 * b), alternatively can be defined as d/dt slerp(a, b, t) = K*slerp(a, b, t), where K is the log of a scaling-rotation matrix.

Uses and linearity


Rotation vectors are typically used to describe angular velocity, including in the Roblox engine, but they are also good at representing changes in orientation angularly, at least in pure math, and are about as accurate as quaternions are. I think we agree on this.

Rotation vectors are a vector space. You can multiply by a scalar and add rotation vectors to get another valid rotation vector.

(Fun fact: A rotation vector in 2D is just a scalar. In 3D, it is a 3-vector. In nD, it is an n*(n - 1)/2 - vector, and become ambiguously ordered!)

  1. You will scale and add rotation vectors when two spinning rigid bodies collide in space. Roblox does this in their physics engine.
  2. You can convert rotation matrices to rotation vectors, and add them, to get a rotation vector describing a kind of commutative multiply operation between rotation matrices. Roblox does this to blend animations together. png-1
  3. You will scale a rotation vector in the process of interpolating rotations. This is the standard “slerp” algorithm for rotation matrices

Conversions to and from rotation vectors


Most of the utility of rotation vectors comes from the fact that they play nicely with rotations.
R is a rotation matrix
Q is a quaternion
r is a rotation vector

Rotation Vector to Matrix

Convert rotation vector to log-rotation matrix and exponentiate:
png
Expanded, this looks like:

Rotation Vector to Quaternion

Convert rotation vector to a pure imaginary quaternion and exponentiate:
png

Rotation Matrix to Rotation Vector

png

Quaternion to Rotation Vector

png-1

If one were designing a RotationVector library, definitely the following methods would be in place:
:ToQuaternion(), :ToLogQuaternion(), :ToCFrame() and :ToLogCFrame(). (The CFrame library would be rewritten to allow for scalar multiplication and addition)

How to slerp with rotation vectors


Because Rotation Vectors are a linear space, you can technically slerp rotation vectors. Though in practice, this tends not to have any real application. It’s like doing a log operation inside of a log operation. Once you are in Rotation Vector space, it makes more sense to linear interpolate them. This is how Roblox blends their animations in a commutative way. However, scaling a rotation vector provides a way to interpolate rotations with a constant angular velocity (which is where “slerp” comes from).
A is a matrix
B is a matrix
t is a scalar interpolation factor

How you would use RotationVectors to slerp a matrix is pretty nice. slerp(A, B, t) is:

  1. Get the delta rotation matrix between two matrices, A and B: D = A^-1 * B
  2. Convert D into rotation vector, r: r = D:ToRotationVector()
  3. Multiply r by t, our interpolation factor: r' = t*r
  4. Convert back into a delta matrix, D': D' = r':ToRotationMatrix()
  5. Multiply D' back out of A, to get our interpolated matrix, C: C = A * D'

For quaternions, it is similar:
C = A * (t*(A^-1 * B):ToRotationVector()):ToQuaternion()

How to slerp Vector3’s with a Rotation Vector conventionally with Quaternions


a is a vector
b is a vector
t is a scalar interpolation factor

Solution 1: Converting vectors to pure imaginary quaternions

  1. Take A and B to be pure imaginary quaternions: A = (w: 0, xyz: a) and B = (w: 0, xyz: b)
  2. Perform the same process as with rotation matrices as defined above:
    C = A * (t*(A^-1 * B):ToRotationVector()):ToQuaternion()
  3. Convert C back into a vector

(For fun) Solution 2: Finding a minimal quaternion

  1. We want to find the angularly minimal quaternion, Q, which when applied to a will turn it into vector b. We can get double this quaternion, D = Q^2: D = (w: a dot b, xyz: a cross b)
  2. We can convert the quaternion, D into a rotation vector, d: d = D:ToRotationVector()
  3. We can get rid of the doubling by dividing d by 2: r = d/2
  4. Now we multiply r by our interpolation factor, t: r' = t*r
  5. And now convert back into a quaternion: Q' = r':ToQuaternion()
  6. Apply quaternion Q' to vector a to get our interpolated vector c: c = Q:sandwich(a)

Complications of using rotation vectors for interpolation:


A problem with using rotation vectors for interpolation, both in the quaternion and vector cases, is that you lose some information about scale. You will result in a unit quaternion and a unit vector regardless of the magnitudes of your input vectors / quaternions.

Notice how when converting from a rotation vector to a matrix, we have 0’s in the diagonal. Similarly, we have a 0 w component in the conversion formula from rotation vector to quaternion.

These 0’s come from ln(scale), when the scale is 1 (ln(1) = 0)

Look at what happens when we place w in the diagonals of a log-rotation matrix, turning it into a log-scaling-rotation matrix.
png
Simiarly with quaternions:
ql_7c02a1f98ce562f99382a998e44553fa_l3

We now include scale information.

We can remove this loss of information by never converting into a rotation vector in the first place:
A * (t*(A^-1 * B):ToRotationVector()):ToQuaternion()
A * exp(t*log(A^-1 * B))
A * (A^-1 * B)^t

Ultimately…


Defining slerp in this way still satisfies the slerp definition. The interpolation of vector a to b is spherically linear, that is, constant angular velocity (projection of the point onto the unit sphere results in a constant velocity magnitude).
Ultimately, it still generally satisfies c' = K*c, the basic definition of spherical motion. (While linear interpolation of magnitude does not.)
where c = slerp(a, b, t)
and c' = d/dt c
and K is a log-scaling-rotation-matrix, a matrix of the form png

This post is getting too long and is taking too long to write.

12 Likes

yeah so basically what @AxisAngle uhhh… said

I apologize for sending you off on a tangent about rotation vectors, as the only point I was trying to make with that mention is that there are multiple ways to encode orientations with 3 real values, and that the proposed Vector3:Slerp() wouldn’t work with any of them. Roblox’s Part.Orientation Vector3s (euler angle triplets) are the more problematic example.

Where are you getting this notion from? Slerp was originally defined only for unit quaternions representing orientations, and even in the case of the geometric definition–the trig one that Shoemake credits to Glenn Davis–it’s defined only for normalized input, indicated in the papers by:
unitquaternions

Can the definitions be evaluated for non-unit quaterions or directional vectors? Yes. Do they give meaningful results? Again, yes. But does anyone actually call them slerp()? As far as I can tell, only Unity. Well, now also Godot, but Godot’s developer was brow-beat into allowing non-unit arguments to his original implementation by Unity refugees (via github feature requests and pull requests). I can’t find any other math library that calls this generalization “slerp”.

Like I said above, I’m not at all opposed to this function existing, I just think it should have a more descriptive name so that there is no ambiguity or misunderstanding about what the Vector3 arguments are meant to represent. There are already multiple functions in the Roblox API that take Vector3 arguments which pitfalls for new developers. The worst is probably Raycast()… I’ve lost count of how many times i’ve seen people either pass a unit vector for the ray direction, or try to pass Vector3s that are start and end points in world space.

I just know that if we add simply Vector3:Slerp(), new developers trying to do Part1.Orientation:Slerp(Part2.Orientation, t) will just be one more of these pitfalls, because it will autocomplete and the function name is too vague.

What should it be called then?

(b + a*-1)*t + a is lerp, arithmetic linear interpolation
(b * a^-1)^t * a is glerp, geometric linear interpolation, maybe?

The fact that glerp is a superset of slerp means that any code which uses slerp can use glerp.

The problem with using a different name is that there is already a wide body of work using slerp, and porting existing code using slerp into Roblox will be more difficult, and it will be harder for people to use existing resources for help on roblox.


I know this is not a library, but in the wikipedia page:


And I just don’t see how changing the name from slerp to glerp or LogAngularLinearInterpolation or anything else will actually prevent this from happening.

If anything, it was a mistake to expose Orientation as a Vector3. A new class containing the euler angles X, Y, Z, as well as the application order, such as EulerOrder = Enum.EulerOrder.YXZ, etc. would have been a better choice, and would have disambiguated which eulerangles we are talking about. Then interp or slerp or something could have been exposed to do proper interpolation.

One that I continually have to try and recreate is the function inverselerp, which does the exact opposite of lerp, instead of returning a value between points a and b based on a scale of 0 - 1, it returns a value between 0 - 1 based on how close point x is between points a and b

function inverseLerp(a: Vector3, b: Vector3, x: Vector3)
--Normal lerp: x = a+(b-a)*t
	--Inverse Lerp: t = (a-x)/(b-a)
	--we have to get the dot products of their differences
	local a1 = (a - x)
	local b1 = (b - a)
	
	local t = a1:Dot(b1) / b1:Dot(b1)
	
	return t
end

Having an in-engine way of doing this would save me a lot of time

Hmm, I think probably the most straingtforward way to do this is with:
png
which appears to simplify to:
png
which also can be modified to remove the division:
png

Keep in mind that math.atan2 takes the arguments in reverse order.
in code this looks something more like

-- c = slerp(a, b, t)
-- gets result based on closest angle, does not include scale.
local function inverseSlerp(a, b, c)
    local adotb = a:Dot(b)
    local acrossb = a:Cross(b)
    local adotc = a:Dot(c)
    local acrossc = a:Cross(c)

    --local proj = acrossc:Dot(acrossb)/acrossb:Dot(acrossb)*acrossb
    --return math.atan2(proj.magnitude, adotc)/math.atan2(acrossb.magnitude, adotb)

    -- slightly simplified
    local projang = math.atan2(acrossb:Dot(acrossc), acrossb.magnitude*adotc)
    local fullang = math.atan2(acrossb.magnitude, adotb)
    return projang/fullang
end

I checked this code and it works.

1 Like

Yes, but nearly everywhere but Unity, slerp is a function in a quaternion class, well understood to be used to interpolate between orientations represented as unit quaternions. Most game developers’ encounters with slerp will be in the context of quaternions. Unity’s Vector3 “slerp” is a bit of an oddball. Roblox’s Vector3 class is pretty clean, with mostly functions and operators that are applicable to general ℝ³ positions, directions, and velocities, and it just feels wrong to me to through in something so special purpose as a core method with a short name. It feels like a helper function intended for a specific use of Vector3s.

If you use Unity’s style slerp on unit direction vectors, you get a nice interpolation that has constant change in angle, and the interpolated point also follows a circular arc with constant speed. But if you allow non-unit arguments, you still have the constant angular velocity, but the interpolated point no longer has constant speed along its now-elliptical path through space. So if what you want is something like nice constant-velocity interpolation of something like a planet on an elliptical orbit, this won’t help, it’s no longer an arc-length parameterization like slerp is.

TBH, I’m not sure what the use case is for wanting this Unity function that interpolates both direction and length. Does anyone know where Unity’s interpolation method is actually the desired one?

True, naming it something like glerp would not help at all. I was leaning more towards a static helper function of the Vector3 class with a descriptive name. Something more like:

local v3 = Vector3.InterpolateDirectionAndLength( v1, v2, t )

The objective would be to make it clear that you’re giving it vectors that are being used as direction x length, and wanting the Unity-style interpolation. This would help avoid the pitfall of it autocompleting for Orientation properties that are Euler triplets. My thought is that if you let that autocomplete, new devs will not even notice that Part.Orientation is a Vector3 of Euler angles; if they see Slerp() pop up as an autocomplete option and just assume Part.Orientation is a rotation CFrame. I mean, I’d probably fall for that.

The Wikipedia section for Geometric Slerp is talking about a generalization to any dimension, but still seems to be assuming the “arcs” to be geodesics on an n-Sphere, no? It does include the cos Ω = p 0 ⋅ p 1 condition, which admittedly doesn’t restrict the vectors to unit vectors, but I was assuming from the context being spherical interpolation that this is being stated as a property of unit vectors, and not a constraint intended to admit elliptical arcs and pairs of non-unit vectors whose length product is 1.

1 Like

What would you want this function to return if the point x does not lie on a line passing through a and b? You could just throw an error if the distance beween x and line through a and b is greater than some small epsilion value or when x isn’t actually between a and b.

One option would be to just generalize it to return |x-a| / (|x-a|+|x-b|) which would give the expected result if x is on the line between a and b, but it will also always give you a value between 0 and 1 that represents the normalized distance from a to x along the linear spline path from a to x to b. This expression is not the inverse of Vector3.Lerp for t outside the range [0,1].

The more common option would be to project x-a onto b-a, and return |x’-a| / |b-a| where x’ is the projected point. This will also give the expected result if x is on the line between a and b, but it also inverts the Lerp function for all real values of t, not just those from 0 to 1. In this respect, it’s the true inverse of the Lerp, but with the caveat that for values of x off the line, it does not make any attempt to preserve the relative distance of x to a vs x to b, it just moves x to the nearest point on the line and solves that problem.

1 Like

I still just want slerp. It would be so nice.

9 Likes