Lerping Over Multiple CFrame Goals

I am creating a plugin that allows me to make quick Bezier camera tracks for tweening the camera along (video below). There are no issues, but there is one final feature I want to implement.

Please take a look at 0:27 - 0:30 in the video above and notice how the white track points all rotate when I rotate the first black buffer plate.

What I want to do: I want to create goals of CFrame rotation at each black buffer plate and then interpolate the white points to have that rotation in between each sett of black plates.

In the image above, you can see I labeled location along the track. The first 33% of white parts I want to interpolate from 0%'s rotation to 33%'s rotation (the percents identify which black plate I labeled)

Problem (I think this is how I should do it?)
I have a table of CFrames. Each CFrame has a percent associated with it. Like shown below:

local goals = {
	[0] = CFrame.new();
	[25] = CFrame.new();
	[50] = CFrame.new();
	[100] = CFrame.new();
}

How would I write code to take an arbitrary percent (say 22) and find the CFrame values that border it (0 and 25, shown above)?

2 Likes

So you want to see the percentage of animation complete at each plate?
I think getting magnitude between each point and them summing them will get you the length, and then you compare the length between a black plate and another to the whole track’s length

1 Like

No, sorry. The exact problem is at the bottom of the OP.

1 Like

I think you would calculate the length between each point and the start point and store it in a table, then get the percentage you want and multiply it by total length, then find which 2 points are after and before that point then lerp their CFrame, for example this table

local Points = { -- Total of 6 straight points, with path length 10 studs
    [1] = 0,
    [2] = 2,
    [3] = 4, -- Length between point 1 and point 3 is 4 studs
    [4] = 6,
    [5] = 8,
    [6] = 10
}

And if the percentage you want is 54%, 0.54 * 10 is 5.4, we get point 3 and point 4 as 5.4 is in the middle, then you lerp the CFrame’s of both points, I think the way you would do it is this

local Point1 = Points[3]
local Point2 = Points[4]
local Studs = 5.4 -- In studs

Studs = Studs - Point1 -- 5.4 - 4 = 1.4
local OffsetCFrame = getCFrameOfPoint(3):Lerp(getCFrameOfPoint(4), Studs / (Point2 - Point1))

The last line lerps the CFrame using this alpha
image

Just tested it in studio, here is the result
https://i.gyazo.com/08211884d3bda0a464e77467bcef5c49.mp4
Here is the script I used for testing
image

local Points = {}

local TotalLength = 0
local CalcPoint = workspace.Points["Point1"].Position
for i = 1, 6 do
	local PointPos = workspace.Points["Point" .. i].Position
	local Length = (PointPos - CalcPoint).Magnitude
	TotalLength = TotalLength + Length
	Points[i] = TotalLength
	CalcPoint = PointPos
end

local function getCFrameOfPoint(i)
	return workspace.Points["Point" .. i].CFrame
end

local P = workspace.Points.Point1:Clone()
P.Name = "Offset"
P.Parent = workspace
P.Color = Color3.fromRGB(255,0,0)

local Dir = 1
local Percent = 0

while true do
	if Percent >= 1 then
		Dir = -1
	elseif Percent <= 0 then
		Dir = 1
	end
	
	Percent = Percent + (0.01 * Dir)
	
	local Studs = math.max(math.min(1, Percent), 0.005) * TotalLength
	local Point1 = 0
	local Point2 = 1

	for i,v in pairs(Points) do
		if v >= Studs then
			Point1 = i - 1
			Point2 = i
			break
		end
	end

	Studs = Studs - Points[Point1]
	
	local OffsetCFrame = getCFrameOfPoint(Point1):Lerp(getCFrameOfPoint(Point2), Studs / (Points[Point2] - Points[Point1]))
	P.CFrame = OffsetCFrame
	
	wait()
end

I appreciate that solution, but unfortunately it has two issues:

  1. It does not actually solve my problem.
  2. It relies a lot on part properties/distances where the parts are just data representations in my example, not really there.

The white parts are rendered and should not be used to base anything off of. The black parts are a visual representation of the multiple CFrame goals I’d like to get one single, linear track of data to interpolate through.

The most straightforward solution is to step through your goals in ascending order of percentage to find the ‘upper bound’, and then step through them again but in reverse (descending) order to find the ‘lower bound’.

The dictionary-style structure of your table makes this more difficult because order isn’t guaranteed when iterating. I have suggested a restructuring below.

Having found the two CFrames, you probably also want to compute an interpolated value between them according to where the point is. I have suggested some code for that too.

-- suggested restructuring: {percentage, cframe}
local goals = {
	{0, CFrame.new()},
	{25, CFrame.new()},
	{50, CFrame.new()},
	{100, CFrame.new()},
}


-- get CFrame at percentage `p`
local function getCFrame(p)
	local below
	local above
	
	-- step forward through the goals
	-- the first value that is greater than or equal to `p`
	-- ... must be the upper-border value
	for i = 1, #goals do
		if goals[i][1] >= p then
			above = goals[i]
			break
		end
	end
	
	-- step backward through the goals
	-- the first value that is less than or equal to `p`
	-- ... must be the lower-border value
	for i = #goals, 1, -1 do
		if goals[i][1] <= p then
			below = goals[i]
			break
		end
	end

	-- handle on-goal values (e.g. 25) where no interpolation is needed
	if above == below then
		return above[2]
	end
	
    -- find interpolated cframe
	local range = above[1] - below[1]
	local alpha = (p - below[1]) / range
	
	return below[2]:Lerp(above[2], alpha)
end

Note that this will error for values of p that are outside of the range of your goals (e.g. -5, 105). You would need to add some extra logic to check for that.

5 Likes

Absolutely wonderful and efficient (my previous conglomerations were too intensive and FPS dropped significantly).

Thanks for your time.

1 Like