Spring Module but for CFrames

So I’m wondering if there is a Spring Modules but with CFrame. I’m trying to do some spring stuff with my viewmodel and it uses Vector3 which can cause problems.

1 Like

There is TweenService and :Lerp but if you are looking for a spring module there’s Flipper. It’s based around Motors and goals.

I would honestly recommend just doing the first two options.

Although a vector3 usually represents the displacement of a spring, for a swaying viewmodel I believe you can make the vector3 represent the angular displacement of the view model rotation, in a similar manner to this:

I’ll see if I can work a solution to this seems interesting.

Here made something, cool to spring up your FPS view model:

It’s a local script which uses Quenty’s spring module to add a spring effect to the otherwise static view model.
Before spring:

After Spring:

Code, insert into your starterCharacter scripts as a local script and test it out. Values for the spring will need adjusting of course.

–Edit: Here’s a better working version with the dampening actually working, messed up the previous CFrame calculation. Still have no idea how to limit the rotation with the spring forces following in suite. Also the video I posted is old and doesn’t contained the fixed changes but am too lazy to reupload so try it out yourself.

Edit 2: Updated it with viewmodel clamping and removed the nan error:

local part = Instance.new("Part")
part.Size = Vector3.new(0.5,0.5,2)
part.Anchored = true
part.CanCollide = false
part.Parent = workspace

local cameraOffset = CFrame.new(0.8, -0.6, -2.2)
local camera = workspace.CurrentCamera

local RunService = game:GetService("RunService")

local Spring = require(script:WaitForChild("Spring"))

local ZEROVECTOR =Vector3.new()
local viewmodelSpring = Spring.new(ZEROVECTOR)
viewmodelSpring.Speed = 5
viewmodelSpring.Damper = 0.25 --1 is perfect dampening

local function clampMagnitude(vector, maxMagnitude)
	return (vector.Magnitude > maxMagnitude and (vector.Unit * maxMagnitude) or vector)
end

function angleBetween(vector1, vector2)
	return math.acos(math.clamp(vector1.Unit:Dot(vector2.Unit), -1, 1))
end


local deltaSensitivity = -2 -- increases force from mouse delta
--if negative force goes in opposite direction, viewmodel is lagging behind
local maxAngle = 30 --degrees

local previousGoalCFrame = CFrame.new()

RunService.RenderStepped:Connect(function(step)
	
	local goalCFrame = camera.CFrame*cameraOffset
	
	part.CFrame = goalCFrame
	
	--Spring stuff
	local differenceCF = previousGoalCFrame:ToObjectSpace(goalCFrame)
	local axis, angle = differenceCF:ToAxisAngle()
	local angularDisplacement = axis*angle
	
	previousGoalCFrame = goalCFrame
	
	local springForce = angularDisplacement*deltaSensitivity
	viewmodelSpring:Impulse(springForce)
	
	local partSpringOffset = viewmodelSpring.Position
	local axis = partSpringOffset.Unit
	local angle = partSpringOffset.Magnitude
	
	--clamp the angle don't want it to speen 360 degrees unless you want it to
	--velocity goes wild though
	angle = math.deg(angle)
	if angle > maxAngle then
		--print("Clamped")
		--local maxAngularDisplacement = axis*angle
		local currentViewModelVelocity = viewmodelSpring.Velocity
		local collision = math.sign(currentViewModelVelocity:Dot(axis))
		--1 is colliding, -1 is going away from colliding wall normal
		if collision > 0 then
			local reactionAngle = angleBetween(currentViewModelVelocity.Unit,axis)
			local resolve = math.cos(reactionAngle)
			local reactionForce = -axis*currentViewModelVelocity.Magnitude*resolve
			viewmodelSpring:Impulse(reactionForce)
		end
	end
	angle = math.clamp(angle,0,maxAngle)	
	angle = math.rad(angle)
	if angle > 0.001 then--Nan check checking if there is no spring caused rotation
		part.CFrame *= CFrame.fromAxisAngle(axis,angle)
	end
	
end)
8 Likes

Woah, what an awesome module! Kinda disappointed I haven’t heard about this module earlier but glad I knew about it today!

https://gyazo.com/7d9d9043dd85c25ea03accfd38d6d58e

2 Likes

Yeah I like the module because it uses os.clock() and cool metatable stuff.

I think an easier way of doing this is to use the mouse delta to get the shoving force direction and magnitude instead of my overly complicated CFrame approach from this really neat FPS tutorial which I should have looked at earlier :stuck_out_tongue: .

		-- Let's get some mouse movement!
		local mouseDelta = game:GetService("UserInputService"):GetMouseDelta()
		self.springs.sway:shove(Vector3.new(mouseDelta.x / 200,mouseDelta.y / 200)) --not sure if this needs deltaTime filtering
--vs my wut CFrame approach
	local differenceCF = previousGoalCFrame:ToObjectSpace(goalCFrame)
	local axis, angle = differenceCF:ToAxisAngle()
	local angularDisplacement = axis*angle
	previousGoalCFrame = goalCFrame
	local springForce = angularDisplacement*deltaSensitivity
	viewmodelSpring:Impulse(springForce)

And to convert the spring force into bobbing you can just use CFrame.Angles instead of CFrame.fromAxisAngles, I believe the CFrame Angles approach gets rid of the rolling rotation as well if thats what you are looking for:

--tutorial sway method:
		local sway = self.springs.sway:update(deltaTime)
		self.viewmodel.rootPart.CFrame *= CFrame.Angles(0,-sway.x,sway.y)
--my CFrame from axis angle method:
	local partSpringOffset = viewmodelSpring.Position
	local axis = partSpringOffset.Unit
	local angle = partSpringOffset.Magnitude
	part.CFrame *= CFrame.fromAxisAngle(axis,angle)

Edit: I do like axis angles a lot.

2 Likes

you literallty just saved me 1000 hours of coding thank you