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.

2 Likes

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:

Old code
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)

Edit 3: I have gotten the time to be reinterested in this problem as it popped up again in #help-and-feedback:scripting-support so here is the video of it after the CFrames are applied to a viewmodel Humanoid root part, with a gun welded to it which changes the center of rotation instead of rotating through the center of the gun. Also the speed is increased so it follows the camera faster and doesn’t lag too far behind.

You can replace psuedoHRP with your own view model “Camera”, apply some offsets and do whatever.

After After After spring improvements

I recommend playing around with these variables:

viewmodelSpring.Speed = 10 --changes how fast it accelerates.
viewmodelSpring.Damper = 0.95 --1 is perfect dampening, 0 is no dampening

local deltaSensitivity = -2 -- increases force from mouse delta
--if negative force goes in opposite direction, viewmodel is lagging behind, 
--Ex, camera turn right, model turns left if negative.
local maxAngle = 30 --degrees, will prevent the gun from being used as a fidget spinner
New improved viewmodel spring code example
local gunPart = Instance.new("Part")
gunPart.Size = Vector3.new(0.5,0.5,2)
gunPart.Anchored = false
gunPart.CanCollide = false
gunPart.Parent = workspace

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

local weld = Instance.new("Weld")
weld.C0 = CFrame.new(1, -0.4, -1.4)
weld.Part0 = psuedoHRP
weld.Part1 = gunPart

weld.Parent = gunPart

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 = 10
viewmodelSpring.Damper = 0.95 --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

	psuedoHRP.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
		psuedoHRP.CFrame *= CFrame.fromAxisAngle(axis,angle)
	end

end)
29 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

3 Likes

Is there a way to achieve the same thing but without the spring effect (same effect as perfect dampening)? I tried removing the spring part of the script and it came out really rough.

For the bobing part it throws an error, part.CFrame *= CFrame.Angles(axis,angle), " 18:25:12.420 Argument 3 missing or nil - Studio"