How to make a one dimensional (number) spring?

I’m here wondering how I would go about making a 1d/number spring, because the tutorials explaining it don’t put it into a context I can understand.

I kinda understand how you would go about making the spring, create a table with the damping (and some other variables than control spring behaviour), with a current point and a target point.
Then every frame some sort of math is done which causes it create a spring effect.

Lets say spring.c is the current value, and spring.t is the target value, and you set spring.c to 1 and set spring.t to 0, you would expect a spring like behaviour such as;

however, lets say the spring.t value is not always going to be 0, and may be updated randomly, it might be a bit harder to do.

I came up with some test code, but i’m not exactly sure how you may go about implementing the actual spring math.

I’m hoping with basic understanding of how to do it 1 dimension (numbers) I may be able to replicate this to the second, or third dimension with angles included.

My test code:

local function create(c, d, r, k)
	--probably not everything here is correct, just did what i thought the code may look like
	local s = {}
	
	--pretty sure theres three variables that control the spring behaviour
	s.d = d
	s.r = r
	s.k = k
	
	--set current, target, (and velocity/accelleration i think???)
	s.c = c
	s.t = c
	s.v = 0
	
	s.update = function()
		--do something to calculate the "c" value (where i need the help)
	end
	return s
end
	
--create the spring

local spring = create(10, 0.5, 0.5, 0.5)

--update the spring every frame

game:GetService("RunService").RenderStepped:Connect(function()
	spring.update()
    print(spring.s) --show the spring value each frame
end)

--test data, setting the springs target value
spring.t = 0

--let the first update occur
wait(5)

--100 "sample updates"

for i = 1, 100 do
	spring.t = math.random(-50, 50) --setting the target value
	wait(math.random(50, 500)/100) --anywhere between half a second to 5 seconds
end

Pretty sure the spring variables how much it tries to pull it towards the target (like pulling to it less means it would take more time for the number to reach the target, and how tight it is (whether the rebound goes large or small distance, and something else i can’t remember.

I just don’t understand the spring math to it and since the explanations to it (which seemed to be very in depth) didn’t put it into a context I understand (programming), its become a stopping point for my programming (which I need to do alot of catch up in the next 6 months to meet personal targets).

Thank you to anyone who can put this into a context I can understand.

EDIT :

I came up with an equation that would work in my case (I think)
y = (c-t) * cos(B * x) * (e^(A * x)) + t

As shown by desmos, this is the graph produced:

where t would change the c value would try and reach that value by doing what is shown in the graph.

As far from what I know, A is the amplitude, or extremity of the graph and B is how fast is oscillates after (frequency/hz)

Hope this helps figure out my problem

EDIT #2:

Figured out non-iterative spring. Would like to know how to do it iteratively though.
My code:

--defining the parts to move
local target = workspace.Target
local current = workspace.Current

--function to create the spring
local function create(c, A, B)
	local s = {}
	
	--A for amplitude, B for frequency
	s.A = A
	s.B = B
	
	--c is the starting value, t is the target value, and s is the current value
	s.c = c
	s.t = c
	s.s = c
	
	--update the spring using time
	s.update = function(x)
		s.s = (s.c - s.t) * math.cos(s.B * x) * math.exp(-s.A * x) + s.t
	end
	return s
end

--creating the spring
local spring = create(0, 0.95, 8)

--setting the time
local d = 0

game:GetService("RunService").RenderStepped:Connect(function(s)
	--adds all the frame times together
	d = d + s
	--update the spring
	spring.update(d)
	print(spring.s)
	--update part positions to visualize the spring
	target.Position = Vector3.new(spring.t, 0, 1.05)
	current.Position = Vector3.new(spring.s, 0, 1.1)
end)

--wait before updating
wait(1)

--set time to 0 before updating (important)
d = 0
--set the starting value as the current value
spring.c = spring.s
--set the target value
spring.t = 2

wait(5)

d = 0
spring.c = spring.s
spring.t = 0
3 Likes

Essentially a one dimensional number spring is just a fluctuating number, not sure how you would simulate input or force being exerted on it… However, I’m pretty sure that Hooke’s law can still be applied to a one dimensional spring. This should help: https://en.wikipedia.org/wiki/Hooke’s_law

2 Likes

spring.t is your graph’s Y offset. A Y offset is achieved by simply adding a constant to the function. Simply add spring.t to the output value.

To calculate spring.c (the Y value in your function) you need the input value, which I’ll call t for time. Your graph here is a cosine wave (since it starts high, vs sine which starts at 0) that is dampened as t goes on (basically divided by a function dependent on t). You could make this a linear dampening, quadratic, or anything else you wanted. As @ndrwcswll mentioned above, Hooke’s law is basically the linear case. Actual springs will deviate from linear values with either extreme compression or expansion (you can’t compress something forever), but it is a good approximation.

So, without looking up any actual spring formulas, where is what will achieve a spring like function for you:

local dampening = 1
local amplitude = 1
local function spring(t, offset)
    return amplitude * math.cos(t) / (t * dampening) + offset
end

Just tweak the amplitude and dampening to your liking.

7 Likes

How would the t be set in this case?
To make more sense, if you’ve played phantom forces you will notice than when you move the weapon the spring equilibrium changes and the gun moves to that place in a linear spring like movement. I’m talking about this in a similar context but only with numbers involved. Wheres theres a target (or the equilibrium) and a current value that wants to be on the equilibrium. Just wondering if it would work in the scenario?

Yes, it would. For t you can plug in the delta time between the start of the spring motion and the current time. I’d use tick() for this. However, in that application you would want the spring hight to start at zero instead of high, so I would use math.sin instead of math.cos. When the gun reaches the stopping position, it doesn’t jump a distance away then slide back into position, but rather slides away then back.

1 Like

You can do this updates iteratively, or non-iteratively. However, it looks like you’re trying to do it non-iteratively. For this, you need to solve the differential equation for a spring.

http://www.sharetechnote.com/html/DE_Modeling_Example_SpringMass.html

You can find a solved version here:

2 Likes

meaning something like this?
image

I think the end result will look something like that, it depends on how you move the gun to the desired angle. Once gun reaches it’s desired angle, it stops, and the spring action begins. At this point, the spring height is at resting position, which is what you call spring.t. Also at this point, no offset from the guns stopped position has occurred and t = 0. As time continues, t in the equation above increases, causing the offset to increase, moving the gun away. The offset will then come back down and the gun will move back into position. So, once the spring action begins, the graph looks like a sine wave with a y offset of the initial position. Getting up to that point may be linear, quadratic, or how ever else you decide to move the gun.

I’m off to take a number theory test… Wish me luck!

1 Like

Just a side note, you might want to look at Lissajous Curves if you’re interested in how Phantom Forces does arm bobbing.

2 Likes

you mean the spring movement right? if so thanks

1 Like

How would you do this iteratively? it seems like a much better method…?