Calculating the proper UIGradient lerp for recomputed endpoints

I’m trying to make a looping gradient like

External Media

as seen in UIGradientPlus. I don’t want to use that module since it has way more logic than I’ll ever need.

Currently, my code calculates new ColorSequences for each frame to render, and then loops through them.

return function(sequence: ColorSequence)
	local cache: { ColorSequence } = { [0] = sequence }
	for delta = 1 / 128, 1, 1 / 128 do
		local seq = {}
		for index, keypoint in sequence.Keypoints do
			if keypoint.Time == 1 or keypoint.Time + delta == 0 then
				continue
			end
			seq[#seq + 1] = ColorSequenceKeypoint.new((keypoint.Time + delta) % 1, keypoint.Value)
		end
		table.sort(seq, function(a, b)
			return a.Time < b.Time
		end)

		table.insert(seq, 1, ColorSequenceKeypoint.new(0, seq[1].Value:Lerp(seq[#seq].Value, delta)))
		seq[#seq + 1] = ColorSequenceKeypoint.new(1, seq[1].Value)

		cache[delta] = ColorSequence.new(seq)
	end

	local self = Instance.new("UIGradient")

	local loop = coroutine.create(function()
		local i = 0
		while true do
			self.Color = cache[i]
			task.wait()
			i = (i + 1 / 128) % 1
		end
	end)
	self.Destroying:Connect(function()
		coroutine.close(loop)
	end)
	coroutine.resume(loop)

	return self
end

The issue is finding the appropriate values of alpha in my :Lerp() function (near the bottom of the for delta = loop).

Using just delta as the alpha value gets me really close

But there’s a flashing that happens with points in the middle when they go from one side to the other.

I’m invoking my code in this example with

require(gradient)(ColorSequence.new({
	ColorSequenceKeypoint.new(0, Color3.new(1)),
	ColorSequenceKeypoint.new(0.25, Color3.new(0, 1)),
	ColorSequenceKeypoint.new(1, Color3.new(1))
})).Parent = ...

Any ideas? I assume the delta works so well because it’s the proper result when the end keypoint gets close to the border.

1 Like

I’m not at home and I have an idea but I can’t implement it right now. Aside from that, I’m wondering: Why do you use increments of 1/128? No integer multiple of 1/128 is equal to 1.

'Cause there’s no such thing as 0.01-0.99 in floating point. I got sick of seeing “0.07000000000000001” in my tables. That was when I was trying to implement keys as times and values as colors, so it might be irrelevant now.

> print({[0.07] = true})
▼ {
	[0.07000000000000001] = true
}

I still like the rationals with 2, though.

Some more that might help, where .Time is implied:

  • seq[1] is guaranteed to be the point closest to 0 greater than 0
  • seq[#seq] is guaranteed to be the point closest to 1 less than 1
  • seq[1] < seq[#seq] always holds
  • The color at both endpoints, 0 and 1, must be the same to look accurate (AFAIK)
  • seq[1] < 1 - seq[#seq] is true when seq[1] is closer to the endpoints than seq[#seq] and vice versa
  • math.min(seq[1], 1 - seq[#seq]) finds the lowest absolute value between endpoints
    • This may not need to be considered

My sketched test cases, where a is seq[1] and b is seq[#seq]:

    • a = 0
    • b = 0.25
    • a is an endpoint
    • a:Lerp(b, 0)

    • a = 0.75
    • b = 1
    • b is an endpoint
    • a:Lerp(b, 1)

    • a = 0.2
    • b = 0.8
    • Both have an absolute value of 0.2
    • a:Lerp(b, 0.5)

    • a = 0.5
    • b = 0.75
    • Absolute value from a is 0.5
    • Absolute value from b is 0.25
    • b is closer by 50% (0.5/0.25, max/min)
    • a:Lerp(b, 0.75)
      • As far as I know, the 0.75 is related to b being 50% closer than a, not b being 0.25 away from 1

So, in other words, my question is: what is the formula given a and b for alpha in :Lerp()?

You can play around in Desmos with the following formulae:

  • (a, 0)
  • (b, 0)
  • a = 0 (slider)
  • b = 1 (slider)
  • min(a, 1 - b) (smallest abs. val.)

Remember that a < b always.

a / 2(1-b) seems promising but falls apart. I don’t think rationals are the answer, since they blow up way above 1 or way below 1. The range for alpha must be between 0 and 1.

(min(a, 1 - b) / max(a, 1 - b)) / 2 seems very promising, but it could be a red herring. It looks like it calculates the value for alpha, but only using whichever stop has the lowest absolute value. I’d like to specifically use a:Lerp(b, alpha) if possible.

The closest I’ve gotten is min(a / (1 - b), (1 - b) / a)) / 2, but again it seems to require extra conditionals to work.

I DID IT :tada:

The color is, using a ternary,

if seq[1].Time < 1 - seq[#seq].Time
	then seq[1].Value:Lerp(seq[#seq].Value, seq[1].Time / (1 - seq[#seq].Time) / 2)
	else seq[#seq].Value:Lerp(seq[1].Value, (1 - seq[#seq].Time) / seq[1].Time / 2)

or, with my earlier notation,

seq[1].Value:Lerp(
	seq[#seq].Value,
	if a < 1 - b
		then a / (1 - b) / 2
		else 1 - ((1 - b) / a / 2)
)

but if anyone has any ideas on how to make that more optimized I’m all for it.

Right now, I’m not going to touch anything. There’s obviously more math than necessary here, but I’ll take my piecewise function and be happy with what I’ve got.

I really need a computer that can give arbitrary regressions for 2-dimensional inputs.

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