Epoxyish, a modern spring solution

Oh my, thanks for letting me know! I’ll fix that soon. For now, you should be able to find it in the Roblox model.

I have no idea how it escaped the directory though haha

2 Likes

Is there anyway to set a get a value like Fusion? Something like

local SetSize = function()
	return if Background:Get() then UDim2.fromScale(0.2, 0.05) else UDim2.fromScale(0.1, 0.05)
end

Button.MouseEnter:Connect(function()
	Background:Set(true)
end)

it’s been 9 months but epoxish should be getting updated soon since ive informed igottic about it lol
i did see your fixed version of it though, you should have linked it to your post imo :stuck_out_tongue:

This is really good except for one thing. CFrames. The rotation of CFrames I really really cant get to work quite right with this, it just seems to cause really weird behavior sending an object really far away for no reason.

1 Like

CFrames definitely don’t work as expected… :cry:

local Epoxyish = require(script.Parent.Epoxyish)
local Value = Epoxyish.Value
local Spring = Epoxyish.Spring
local Latch = Epoxyish.Latch

--\\ Private Methods

local function getCFrame()
	return workspace.Baseplate.Attachment.WorldCFrame
end

--\\ Connections

local spring = Spring {
	Target = Value(getCFrame()),
	Speed = 5,
	Dampening = 3,
}

Latch (script.Parent) {
	CFrame = spring
}

while task.wait() do
	spring.Target:Set(getCFrame())
end

Position seems to work alright, but when Rotation gets involved… brace yourself.

Video on Streamable

Thanks for the report! Will look into it.

1 Like

I should that add this seems like what @ianfinity01 was describing from a month ago!

2 Likes

After experimenting a bit, I found something that may be very important in figuring out how to solve this.

1: IterationForce calculated incorrectly?

Here is the formula:

It makes sense! The force is target - position. However, with CFrames, an unexpected value is made with this.

{E0E27DDC-0A1E-4FB3-8385-841C1DA3CF97}

Since the Y for both these CFrames is the same, you would expect the new Y to be zero. It turns into 0.95 for some reason! And the Z for both is zero, but it turns into -3…? The RX value seems to be correct though. 38.38 - 2.38 is indeed 36. What happened here?

Here’s the original CFrame Add function:

{0E1B6096-C270-4BCE-A8CC-B8AAE83546FE}

Here’s my fixed version:

This fixes the IterationForce and the “flying” bug, but Acceleration has another problem. This is where I got majorly stumped, and I bet you will too…

2: Acceleration problem

At this point, the “flying” bug is fixed. It’s when the position flies off into infinity at most angles. This problem describes why the spring doesn’t rotate correctly.

Here is the formula for acceleration:

Again, this makes sense! F = ma, and we’re solving for a, so the new formula is a = F/M, which can be seen above as Acceleration = F1 * F2 * 1 / M. What went wrong?

This issue lies in the Multiply CFrame function:

Here, Number is set to 50 which is the spring’s force. RotationX is set to 36, and 36 * 50 is 1800. So, our rotation X acceleration is going up by 1800 radians per second. Checks out.

Or does it??

local angles = CFrame.Angles(math.rad(36) * 50, 0 * 50, 0 * 50) 
print(angles:ToOrientation()) --> -5.56283680452907e-07 0 0

No. The acceleration is (basically) 0 radians per second, so it doesn’t rotate. :cry:

This is where I hit the end of my knowledge. CFrames are very confusing. All I can tell is that 1800 is divisible by 360, so maybe that has something to do with it.

If I set the force really low, like to 2, it looks like it wants to work…

…then it starts doing backflips. Working with the orientation values seems to be pretty unreliable. :sweat_smile:

The only solution I can think of is not using CFrames to represent CFrames. Instead, two Vector3s representing position and orientation respectively. I have no idea if it would work though. Maybe I’ll try later in another reply?

You can try the test I used here (with my current fixed code):

EpoxyishCFrameTest.rbxm (15.8 KB)

1 Like

Thanks a ton! This is mega helpful.

1 Like

I will use the Quaternions spring module to solve the issue. Basically, I plan to just integrate it in Epoxyish when your asking to make a CFrame spring. When I tried solving the CFrames issues, I came across ‘Gimble Lock’. :cry:

I’ll undo everything in my old post because this approach seems more convenient. Tomorrow. It’s 11 PM. :3

Old Post

I was able to fix this! Sort of.

https://youtu.be/xUbtEVBGis0

Epoxyish Fixed CFrame Test (16.2 KB)

By representing CFrames as a list of 6 numbers (X, Y, Z, RX, RY, RZ) it works good enough.

The target, position, and velocity are all “decompiled” into something usable. This could be the same value, but if it’s a CFrame, it’s turned into the list.

Since CFrames were lists now, I had to make a new method to get the type of values.

{A3775D9B-1619-4562-B99A-3232F0165438}

The Multiply, Add, and AsNumber methods had to also account for these lists, so I changed that in those modules:

Sidenote

The logic behind making springs sleep was a bit buggy, so I fixed that too. It made it seem like springs stopped abruptly, randomly. The new logic is as follows:
(edit): Is it actually fixed…? I will have to actually test. Lol.

The highlighted line is the one I added!


This solution fixes the acceleration problem, since these numbers have no limits.

The video shows that the RX spring test behaviors a bit weirdly…

When you rotate the rotor between 90 and 270 degrees, the test fails.

image

We can look into this later, I’m going to have stuffed shells!

Summary

1 Like

I’ve implemented the Quaternion Springs to fix the issue!

I will still need to battle test this to ensure its bug-free, but this is good news!
If I get lazy and you want to test it instead, you can try with this model:

EpoxyishWithQuaternions.rbxm (43.4 KB)

1 Like

This is awesome, thank you so much!

1 Like

Here’s a list of things I did to my version of Epoxyish!


Indexing the Position property would give you whatever is there, but it doesn’t update anything. So, I used the __index method to update things when you get the Position or Velocity.

This makes a bit more intuitive sense to me, because you may not know to use the :Get() method. Now that these properties can be seamlessly accessed, I made the :Get() method simply return the position, and nothing else. The calculations were moved to the _Update() method which the __newindex method uses. This way, using Latch may or may not be more readable than just using the spring Position directly. I still like it though! It looks professional.


I also added errors when setting Position and Velocity to incorrect types. Setting Target to a different type will still reset Position and Velocity to match the type, but attempting to change Position and Velocity with incorrect types will throw an error. In the current Epoxyish, I felt that the silent ignoring of this maybe could have caused some headaches for some people, but it would’ve been rare.

Bonus error!
It should tell you if it failed to make a spring with the current target.


I added a Scale parameter to the :Impulse() method. This parameter will scale the force, no matter what type it is. This change was motivated by trying to impulse with CFrames. Since simply multiplying the orientation of a CFrame may have the rotations wrap around, I added this parameter to workaround that. Since Quaternions use Vector3s sometimes to represent orientation in 3 axes, you can just multiply that by the scale.


I made the default spring properties accessible in the main Epoxyish module. I feel this will make configuring your springs a bit easier even if you don’t know any formulas. I was getting confused on how to change the properties because I couldn’t easily look at the defaults. With this, you don’t need to! You can just scale them how you want.

image

image


You already know this, but I implemented Quaternion Springs to finally fix the bad CFrame behavior! This was the very first thing I tried to use the module for, so I was upset to see it not work. That’s kind of why I keep coming back here to perfect this module. I want to use it!

Basically, if you set the target to a CFrame, it’s going to create a QuaternionSpring to use in the background. Then, it also runs the original CFrame behavior (with no rotation) in the background. When you ask for the spring position, it merges the two spring positions together! If you ask for the velocity, it also merges the velocities together roughly. Remember, the velocity is not really possible to represent in a CFrame, and I’d rather not send a Quaternion to the user who doesn’t know about them… :sweat_smile:

image


I also added a better awake handling system. Springs will be asleep if their position is close enough to the target, and the velocity is slow enough. Before, springs may abruptly stop when they get slow enough to the target. This fix makes springs stop much more smoothly.

I also added a phantom property called Awake which is also based on the QSpring. If either the Epoxyish spring or the QSpring is still moving, the entire spring is still awake. This is the intended property to use now instead of Asleep.

image


Since this is a lot, I tested all of the new code!

The last test in the video is exactly what I wanted to use this module for in the first place. It works now!


I’d say this is safe enough to publish, but full free to test yourself if you don’t believe me. Have fun! :+1:

Epoxyish.rbxm (27.2 KB) Tests (50.2 KB)

2 Likes

I’m back again! :sweat_smile:

After I finished implementing CFrame springs, the next thing I tried using in my game was impulsing UDim2 springs. Didn’t work!

I’ll save any other replies and just send another fixed version. You don’t have to use it or anything, it’s just important to note in case anybody thinks they did something wrong with their code (because I did). People who wanted the bug fix may use my version!


Changelog

+ Fixes CFrames Springs using Quaternions
+ Fixes UDim2, UDim springs by switching them to tables internally
+ Fixes sleep calculations
+ Solves property ambiguity
+ Exposes default spring settings
+ Implements type mismatch errors
+ Asleep, Sleeping, Awake alias readonly properties
- Removes the ability to read Acceleration
- Removed Herobrine

Epoxyish.rbxm (27.7 KB)

Epoxyish Test Place.rbxl (100.9 KB)

2 Likes