How to replicate Unity Animation Curves?

Similar to a post here, I need animation curves that are inputted a number and returns a value of the curve at that certain index. This is needed to find co-effiecents for lift, drag and torque for realistic aerocraft physics. Here’s an example of a graph simialr to what I’m trying to replicate:

Before anyone suggests bezier curves, that would not work for my case.

If anyone knows fast modules that can perform these graph calculations, please link them below!

i got this working, sort of, heres the module. you can tinker with it. i dont use this anymore

-- services

-- functions
function lerp(a, b, t)
	return a + (b - a) * t
end

local AnimationCurve = {}
AnimationCurve.__index = AnimationCurve

function AnimationCurve.new()
	local self = setmetatable({
		points = {},
		size = Vector2.new(450, 450),
		visualizerCount = 100,
	}, AnimationCurve)
	return self
end

function AnimationCurve:_sort()
	table.sort(self.points, function(a, b)
		return a.time < b.time
	end)
end

function AnimationCurve:addKey(time, value, inTangent, outTangent)
	local key = { time = time, value = value, inTangent = inTangent, outTangent = outTangent }
	table.insert(self.points, key)
	--self:recalculateMinMax()
	self:_sort()
	return key
end

function AnimationCurve:removeKey(time)
	for i, point in ipairs(self.points) do
		if point.time == time then
			table.remove(self.points, i)
			break
		end
	end
	
	self:_sort()
	--self:recalculateMinMax()
end

function AnimationCurve:clear()
	self.points = {}
end

function AnimationCurve:evaluate(time)
	if #self.points == 0 then
		error("Animation curve contains no keyframes")
	end

	self:_sort()
	if time <= self.points[1].time then
		return self.points[1].value
	end
	if time >= self.points[#self.points].time then
		return self.points[#self.points].value
	end

	-- find the keyframe point
	local left_point, right_point = nil, nil
	for i, point in ipairs(self.points) do
		if point.time > time then
			left_point = self.points[i - 1]
			right_point = point
			break
		end
	end
	local time_range = right_point.time - left_point.time
	if time_range == 0 then
		return left_point.value
	end
	local t = (time - left_point.time) / time_range

	-- cubic hermite
	local t2 = t * t
	local t3 = t2 * t
	local a = 2 * t3 - 3 * t2 + 1
	local b = -2 * t3 + 3 * t2
	local c = t3 - 2 * t2 + t
	local d = t3 - t2

	return a * left_point.value + b * right_point.value + c * left_point.outTangent + d * right_point.inTangent
end

function AnimationCurve:recalculateMinMax()
	self:_sort()
	local minX, maxX, minY, maxY = math.huge, -math.huge, math.huge, -math.huge
	
	for i = 1, self.visualizerCount do
		local t = (i - 1) / (self.visualizerCount - 1)
		local x = lerp(self.points[1].time, self.points[#self.points].time, t)
		local y = self:evaluate(x)
		minX, maxX = math.min(minX, x), math.max(maxX, x)
		minY, maxY = math.min(minY, y), math.max(maxY, y)
	end

	self.minX, self.maxX, self.minY, self.maxY = minX, maxX, minY, maxY
end

function AnimationCurve:updateVisualizer(parent)
	local size = self.size
	local anchor = Vector2.new(parent.AbsoluteSize.x - size.x, parent.AbsoluteSize.y - size.y)
	
	if self.frames then
		for _,frame in ipairs(self.frames) do
			frame:Destroy()
		end
	end
	self.frames = {}
	
	local minX, maxX, minY, maxY = self.minX, self.maxX, self.minY, self.maxY
	
	for i = 1, self.visualizerCount do
		local t = (i - 1) / (self.visualizerCount - 1)
		local x = lerp(self.points[1].time, self.points[#self.points].time, t)
		local y = self:evaluate(x)

		local frame = Instance.new("Frame")
		frame.Parent = parent
		frame.Size = UDim2.new(0, 10, 0, 10)
		frame.AnchorPoint = Vector2.new(0.5, 0.5)
		frame.Position = UDim2.new(
			0, 
			(x - minX) / (maxX - minX) * size.x + (anchor.x / 2),
			0, 
			size.y - (y - minY) / (maxY - minY) * size.y + (anchor.y / 2)
		)

		table.insert(self.frames, frame)
	end
end

function AnimationCurve:getGuiPosition(time, parent, isEvaluated)
	local size = self.size
	local anchor = Vector2.new(parent.AbsoluteSize.x - size.x, parent.AbsoluteSize.y - size.y)
	
	self:_sort()
	local left_time, right_time = nil, nil
	for i, point in ipairs(self.points) do
		if point.time > time and i ~= 1 then
			left_time = self.points[i - 1].time
			right_time = point.time
			break
		end
	end

	local t
	if time <= self.points[1].time then
		t = 0
	elseif time >= self.points[#self.points].time then
		t = 1
	else
		local time_range = right_time - left_time
		if time_range == 0 then
			warn('its 0')
		end

		t = (time - left_time) / time_range
	end

	local minX, maxX, minY, maxY = self.minX, self.maxX, self.minY, self.maxY

	local x = time
	if isEvaluated then
		if time <= self.points[1].time then
			x = minX
		elseif time >= self.points[#self.points].time then
			x = maxX
		end
	end
	
	local y = self:evaluate(time)

	return UDim2.new(
		0, 
		(x - minX) / (maxX - minX) * self.size.x + (anchor.x / 2),
		0, 
		self.size.y - (y - minY) / (maxY - minY) * self.size.y + (anchor.y / 2)
	)
end

return AnimationCurve
3 Likes

Roblox has a FloatCurve class that basically works the same as Unity’s Animation Curves. You create keys by using :InsertKey(FloatCurveKey) and evaluate it at a given time using :GetValueATime(). Read the documentation for more info.

1 Like

Thanks, made this using your suggestion:


How it is stored in the aerocraft data:
image

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.